Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/numpy/lib/_utils_impl.py: 12%

284 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-09 06:12 +0000

1import os 

2import sys 

3import textwrap 

4import types 

5import re 

6import warnings 

7import functools 

8import platform 

9 

10from numpy._core import ndarray 

11from numpy._utils import set_module 

12import numpy as np 

13 

14__all__ = [ 

15 'get_include', 'info', 'show_runtime' 

16] 

17 

18 

19@set_module('numpy') 

20def show_runtime(): 

21 """ 

22 Print information about various resources in the system 

23 including available intrinsic support and BLAS/LAPACK library 

24 in use 

25 

26 .. versionadded:: 1.24.0 

27 

28 See Also 

29 -------- 

30 show_config : Show libraries in the system on which NumPy was built. 

31 

32 Notes 

33 ----- 

34 1. Information is derived with the help of `threadpoolctl <https://pypi.org/project/threadpoolctl/>`_ 

35 library if available. 

36 2. SIMD related information is derived from ``__cpu_features__``, 

37 ``__cpu_baseline__`` and ``__cpu_dispatch__`` 

38 

39 """ 

40 from numpy._core._multiarray_umath import ( 

41 __cpu_features__, __cpu_baseline__, __cpu_dispatch__ 

42 ) 

43 from pprint import pprint 

44 config_found = [{ 

45 "numpy_version": np.__version__, 

46 "python": sys.version, 

47 "uname": platform.uname(), 

48 }] 

49 features_found, features_not_found = [], [] 

50 for feature in __cpu_dispatch__: 

51 if __cpu_features__[feature]: 

52 features_found.append(feature) 

53 else: 

54 features_not_found.append(feature) 

55 config_found.append({ 

56 "simd_extensions": { 

57 "baseline": __cpu_baseline__, 

58 "found": features_found, 

59 "not_found": features_not_found 

60 } 

61 }) 

62 try: 

63 from threadpoolctl import threadpool_info 

64 config_found.extend(threadpool_info()) 

65 except ImportError: 

66 print("WARNING: `threadpoolctl` not found in system!" 

67 " Install it by `pip install threadpoolctl`." 

68 " Once installed, try `np.show_runtime` again" 

69 " for more detailed build information") 

70 pprint(config_found) 

71 

72 

73@set_module('numpy') 

74def get_include(): 

75 """ 

76 Return the directory that contains the NumPy \\*.h header files. 

77 

78 Extension modules that need to compile against NumPy may need to use this 

79 function to locate the appropriate include directory. 

80 

81 Notes 

82 ----- 

83 When using ``setuptools``, for example in ``setup.py``:: 

84 

85 import numpy as np 

86 ... 

87 Extension('extension_name', ... 

88 include_dirs=[np.get_include()]) 

89 ... 

90 

91 Note that a CLI tool ``numpy-config`` was introduced in NumPy 2.0, using 

92 that is likely preferred for build systems other than ``setuptools``:: 

93 

94 $ numpy-config --cflags 

95 -I/path/to/site-packages/numpy/_core/include 

96 

97 # Or rely on pkg-config: 

98 $ export PKG_CONFIG_PATH=$(numpy-config --pkgconfigdir) 

99 $ pkg-config --cflags 

100 -I/path/to/site-packages/numpy/_core/include 

101 

102 """ 

103 import numpy 

104 if numpy.show_config is None: 

105 # running from numpy source directory 

106 d = os.path.join(os.path.dirname(numpy.__file__), '_core', 'include') 

107 else: 

108 # using installed numpy core headers 

109 import numpy._core as _core 

110 d = os.path.join(os.path.dirname(_core.__file__), 'include') 

111 return d 

112 

113 

114class _Deprecate: 

115 """ 

116 Decorator class to deprecate old functions. 

117 

118 Refer to `deprecate` for details. 

119 

120 See Also 

121 -------- 

122 deprecate 

123 

124 """ 

125 

126 def __init__(self, old_name=None, new_name=None, message=None): 

127 self.old_name = old_name 

128 self.new_name = new_name 

129 self.message = message 

130 

131 def __call__(self, func, *args, **kwargs): 

132 """ 

133 Decorator call. Refer to ``decorate``. 

134 

135 """ 

136 old_name = self.old_name 

137 new_name = self.new_name 

138 message = self.message 

139 

140 if old_name is None: 

141 old_name = func.__name__ 

142 if new_name is None: 

143 depdoc = "`%s` is deprecated!" % old_name 

144 else: 

145 depdoc = "`%s` is deprecated, use `%s` instead!" % \ 

146 (old_name, new_name) 

147 

148 if message is not None: 

149 depdoc += "\n" + message 

150 

151 @functools.wraps(func) 

152 def newfunc(*args, **kwds): 

153 warnings.warn(depdoc, DeprecationWarning, stacklevel=2) 

154 return func(*args, **kwds) 

155 

156 newfunc.__name__ = old_name 

157 doc = func.__doc__ 

158 if doc is None: 

159 doc = depdoc 

160 else: 

161 lines = doc.expandtabs().split('\n') 

162 indent = _get_indent(lines[1:]) 

163 if lines[0].lstrip(): 

164 # Indent the original first line to let inspect.cleandoc() 

165 # dedent the docstring despite the deprecation notice. 

166 doc = indent * ' ' + doc 

167 else: 

168 # Remove the same leading blank lines as cleandoc() would. 

169 skip = len(lines[0]) + 1 

170 for line in lines[1:]: 

171 if len(line) > indent: 

172 break 

173 skip += len(line) + 1 

174 doc = doc[skip:] 

175 depdoc = textwrap.indent(depdoc, ' ' * indent) 

176 doc = '\n\n'.join([depdoc, doc]) 

177 newfunc.__doc__ = doc 

178 

179 return newfunc 

180 

181 

182def _get_indent(lines): 

183 """ 

184 Determines the leading whitespace that could be removed from all the lines. 

185 """ 

186 indent = sys.maxsize 

187 for line in lines: 

188 content = len(line.lstrip()) 

189 if content: 

190 indent = min(indent, len(line) - content) 

191 if indent == sys.maxsize: 

192 indent = 0 

193 return indent 

194 

195 

196def deprecate(*args, **kwargs): 

197 """ 

198 Issues a DeprecationWarning, adds warning to `old_name`'s 

199 docstring, rebinds ``old_name.__name__`` and returns the new 

200 function object. 

201 

202 This function may also be used as a decorator. 

203 

204 .. deprecated:: 2.0 

205 Use `~warnings.warn` with :exc:`DeprecationWarning` instead. 

206 

207 Parameters 

208 ---------- 

209 func : function 

210 The function to be deprecated. 

211 old_name : str, optional 

212 The name of the function to be deprecated. Default is None, in 

213 which case the name of `func` is used. 

214 new_name : str, optional 

215 The new name for the function. Default is None, in which case the 

216 deprecation message is that `old_name` is deprecated. If given, the 

217 deprecation message is that `old_name` is deprecated and `new_name` 

218 should be used instead. 

219 message : str, optional 

220 Additional explanation of the deprecation. Displayed in the 

221 docstring after the warning. 

222 

223 Returns 

224 ------- 

225 old_func : function 

226 The deprecated function. 

227 

228 Examples 

229 -------- 

230 Note that ``olduint`` returns a value after printing Deprecation 

231 Warning: 

232 

233 >>> olduint = np.lib.utils.deprecate(np.uint) 

234 DeprecationWarning: `uint64` is deprecated! # may vary 

235 >>> olduint(6) 

236 6 

237 

238 """ 

239 # Deprecate may be run as a function or as a decorator 

240 # If run as a function, we initialise the decorator class 

241 # and execute its __call__ method. 

242 

243 # Deprecated in NumPy 2.0, 2023-07-11 

244 warnings.warn( 

245 "`deprecate` is deprecated, " 

246 "use `warn` with `DeprecationWarning` instead. " 

247 "(deprecated in NumPy 2.0)", 

248 DeprecationWarning, 

249 stacklevel=2 

250 ) 

251 

252 if args: 

253 fn = args[0] 

254 args = args[1:] 

255 

256 return _Deprecate(*args, **kwargs)(fn) 

257 else: 

258 return _Deprecate(*args, **kwargs) 

259 

260 

261def deprecate_with_doc(msg): 

262 """ 

263 Deprecates a function and includes the deprecation in its docstring. 

264 

265 .. deprecated:: 2.0 

266 Use `~warnings.warn` with :exc:`DeprecationWarning` instead. 

267 

268 This function is used as a decorator. It returns an object that can be 

269 used to issue a DeprecationWarning, by passing the to-be decorated 

270 function as argument, this adds warning to the to-be decorated function's 

271 docstring and returns the new function object. 

272 

273 See Also 

274 -------- 

275 deprecate : Decorate a function such that it issues a 

276 :exc:`DeprecationWarning` 

277 

278 Parameters 

279 ---------- 

280 msg : str 

281 Additional explanation of the deprecation. Displayed in the 

282 docstring after the warning. 

283 

284 Returns 

285 ------- 

286 obj : object 

287 

288 """ 

289 

290 # Deprecated in NumPy 2.0, 2023-07-11 

291 warnings.warn( 

292 "`deprecate` is deprecated, " 

293 "use `warn` with `DeprecationWarning` instead. " 

294 "(deprecated in NumPy 2.0)", 

295 DeprecationWarning, 

296 stacklevel=2 

297 ) 

298 

299 return _Deprecate(message=msg) 

300 

301 

302#----------------------------------------------------------------------------- 

303 

304 

305# NOTE: pydoc defines a help function which works similarly to this 

306# except it uses a pager to take over the screen. 

307 

308# combine name and arguments and split to multiple lines of width 

309# characters. End lines on a comma and begin argument list indented with 

310# the rest of the arguments. 

311def _split_line(name, arguments, width): 

312 firstwidth = len(name) 

313 k = firstwidth 

314 newstr = name 

315 sepstr = ", " 

316 arglist = arguments.split(sepstr) 

317 for argument in arglist: 

318 if k == firstwidth: 

319 addstr = "" 

320 else: 

321 addstr = sepstr 

322 k = k + len(argument) + len(addstr) 

323 if k > width: 

324 k = firstwidth + 1 + len(argument) 

325 newstr = newstr + ",\n" + " "*(firstwidth+2) + argument 

326 else: 

327 newstr = newstr + addstr + argument 

328 return newstr 

329 

330_namedict = None 

331_dictlist = None 

332 

333# Traverse all module directories underneath globals 

334# to see if something is defined 

335def _makenamedict(module='numpy'): 

336 module = __import__(module, globals(), locals(), []) 

337 thedict = {module.__name__:module.__dict__} 

338 dictlist = [module.__name__] 

339 totraverse = [module.__dict__] 

340 while True: 

341 if len(totraverse) == 0: 

342 break 

343 thisdict = totraverse.pop(0) 

344 for x in thisdict.keys(): 

345 if isinstance(thisdict[x], types.ModuleType): 

346 modname = thisdict[x].__name__ 

347 if modname not in dictlist: 

348 moddict = thisdict[x].__dict__ 

349 dictlist.append(modname) 

350 totraverse.append(moddict) 

351 thedict[modname] = moddict 

352 return thedict, dictlist 

353 

354 

355def _info(obj, output=None): 

356 """Provide information about ndarray obj. 

357 

358 Parameters 

359 ---------- 

360 obj : ndarray 

361 Must be ndarray, not checked. 

362 output 

363 Where printed output goes. 

364 

365 Notes 

366 ----- 

367 Copied over from the numarray module prior to its removal. 

368 Adapted somewhat as only numpy is an option now. 

369 

370 Called by info. 

371 

372 """ 

373 extra = "" 

374 tic = "" 

375 bp = lambda x: x 

376 cls = getattr(obj, '__class__', type(obj)) 

377 nm = getattr(cls, '__name__', cls) 

378 strides = obj.strides 

379 endian = obj.dtype.byteorder 

380 

381 if output is None: 

382 output = sys.stdout 

383 

384 print("class: ", nm, file=output) 

385 print("shape: ", obj.shape, file=output) 

386 print("strides: ", strides, file=output) 

387 print("itemsize: ", obj.itemsize, file=output) 

388 print("aligned: ", bp(obj.flags.aligned), file=output) 

389 print("contiguous: ", bp(obj.flags.contiguous), file=output) 

390 print("fortran: ", obj.flags.fortran, file=output) 

391 print( 

392 "data pointer: %s%s" % (hex(obj.ctypes._as_parameter_.value), extra), 

393 file=output 

394 ) 

395 print("byteorder: ", end=' ', file=output) 

396 if endian in ['|', '=']: 

397 print("%s%s%s" % (tic, sys.byteorder, tic), file=output) 

398 byteswap = False 

399 elif endian == '>': 

400 print("%sbig%s" % (tic, tic), file=output) 

401 byteswap = sys.byteorder != "big" 

402 else: 

403 print("%slittle%s" % (tic, tic), file=output) 

404 byteswap = sys.byteorder != "little" 

405 print("byteswap: ", bp(byteswap), file=output) 

406 print("type: %s" % obj.dtype, file=output) 

407 

408 

409@set_module('numpy') 

410def info(object=None, maxwidth=76, output=None, toplevel='numpy'): 

411 """ 

412 Get help information for an array, function, class, or module. 

413 

414 Parameters 

415 ---------- 

416 object : object or str, optional 

417 Input object or name to get information about. If `object` is 

418 an `ndarray` instance, information about the array is printed. 

419 If `object` is a numpy object, its docstring is given. If it is 

420 a string, available modules are searched for matching objects. 

421 If None, information about `info` itself is returned. 

422 maxwidth : int, optional 

423 Printing width. 

424 output : file like object, optional 

425 File like object that the output is written to, default is 

426 ``None``, in which case ``sys.stdout`` will be used. 

427 The object has to be opened in 'w' or 'a' mode. 

428 toplevel : str, optional 

429 Start search at this level. 

430 

431 Notes 

432 ----- 

433 When used interactively with an object, ``np.info(obj)`` is equivalent 

434 to ``help(obj)`` on the Python prompt or ``obj?`` on the IPython 

435 prompt. 

436 

437 Examples 

438 -------- 

439 >>> np.info(np.polyval) # doctest: +SKIP 

440 polyval(p, x) 

441 Evaluate the polynomial p at x. 

442 ... 

443 

444 When using a string for `object` it is possible to get multiple results. 

445 

446 >>> np.info('fft') # doctest: +SKIP 

447 *** Found in numpy *** 

448 Core FFT routines 

449 ... 

450 *** Found in numpy.fft *** 

451 fft(a, n=None, axis=-1) 

452 ... 

453 *** Repeat reference found in numpy.fft.fftpack *** 

454 *** Total of 3 references found. *** 

455 

456 When the argument is an array, information about the array is printed. 

457 

458 >>> a = np.array([[1 + 2j, 3, -4], [-5j, 6, 0]], dtype=np.complex64) 

459 >>> np.info(a) 

460 class: ndarray 

461 shape: (2, 3) 

462 strides: (24, 8) 

463 itemsize: 8 

464 aligned: True 

465 contiguous: True 

466 fortran: False 

467 data pointer: 0x562b6e0d2860 # may vary 

468 byteorder: little 

469 byteswap: False 

470 type: complex64 

471 

472 """ 

473 global _namedict, _dictlist 

474 # Local import to speed up numpy's import time. 

475 import pydoc 

476 import inspect 

477 

478 if (hasattr(object, '_ppimport_importer') or 

479 hasattr(object, '_ppimport_module')): 

480 object = object._ppimport_module 

481 elif hasattr(object, '_ppimport_attr'): 

482 object = object._ppimport_attr 

483 

484 if output is None: 

485 output = sys.stdout 

486 

487 if object is None: 

488 info(info) 

489 elif isinstance(object, ndarray): 

490 _info(object, output=output) 

491 elif isinstance(object, str): 

492 if _namedict is None: 

493 _namedict, _dictlist = _makenamedict(toplevel) 

494 numfound = 0 

495 objlist = [] 

496 for namestr in _dictlist: 

497 try: 

498 obj = _namedict[namestr][object] 

499 if id(obj) in objlist: 

500 print("\n " 

501 "*** Repeat reference found in %s *** " % namestr, 

502 file=output 

503 ) 

504 else: 

505 objlist.append(id(obj)) 

506 print(" *** Found in %s ***" % namestr, file=output) 

507 info(obj) 

508 print("-"*maxwidth, file=output) 

509 numfound += 1 

510 except KeyError: 

511 pass 

512 if numfound == 0: 

513 print("Help for %s not found." % object, file=output) 

514 else: 

515 print("\n " 

516 "*** Total of %d references found. ***" % numfound, 

517 file=output 

518 ) 

519 

520 elif inspect.isfunction(object) or inspect.ismethod(object): 

521 name = object.__name__ 

522 try: 

523 arguments = str(inspect.signature(object)) 

524 except Exception: 

525 arguments = "()" 

526 

527 if len(name+arguments) > maxwidth: 

528 argstr = _split_line(name, arguments, maxwidth) 

529 else: 

530 argstr = name + arguments 

531 

532 print(" " + argstr + "\n", file=output) 

533 print(inspect.getdoc(object), file=output) 

534 

535 elif inspect.isclass(object): 

536 name = object.__name__ 

537 try: 

538 arguments = str(inspect.signature(object)) 

539 except Exception: 

540 arguments = "()" 

541 

542 if len(name+arguments) > maxwidth: 

543 argstr = _split_line(name, arguments, maxwidth) 

544 else: 

545 argstr = name + arguments 

546 

547 print(" " + argstr + "\n", file=output) 

548 doc1 = inspect.getdoc(object) 

549 if doc1 is None: 

550 if hasattr(object, '__init__'): 

551 print(inspect.getdoc(object.__init__), file=output) 

552 else: 

553 print(inspect.getdoc(object), file=output) 

554 

555 methods = pydoc.allmethods(object) 

556 

557 public_methods = [meth for meth in methods if meth[0] != '_'] 

558 if public_methods: 

559 print("\n\nMethods:\n", file=output) 

560 for meth in public_methods: 

561 thisobj = getattr(object, meth, None) 

562 if thisobj is not None: 

563 methstr, other = pydoc.splitdoc( 

564 inspect.getdoc(thisobj) or "None" 

565 ) 

566 print(" %s -- %s" % (meth, methstr), file=output) 

567 

568 elif hasattr(object, '__doc__'): 

569 print(inspect.getdoc(object), file=output) 

570 

571 

572def safe_eval(source): 

573 """ 

574 Protected string evaluation. 

575 

576 .. deprecated:: 2.0 

577 Use `ast.literal_eval` instead. 

578 

579 Evaluate a string containing a Python literal expression without 

580 allowing the execution of arbitrary non-literal code. 

581 

582 .. warning:: 

583 

584 This function is identical to :py:meth:`ast.literal_eval` and 

585 has the same security implications. It may not always be safe 

586 to evaluate large input strings. 

587 

588 Parameters 

589 ---------- 

590 source : str 

591 The string to evaluate. 

592 

593 Returns 

594 ------- 

595 obj : object 

596 The result of evaluating `source`. 

597 

598 Raises 

599 ------ 

600 SyntaxError 

601 If the code has invalid Python syntax, or if it contains 

602 non-literal code. 

603 

604 Examples 

605 -------- 

606 >>> np.safe_eval('1') 

607 1 

608 >>> np.safe_eval('[1, 2, 3]') 

609 [1, 2, 3] 

610 >>> np.safe_eval('{"foo": ("bar", 10.0)}') 

611 {'foo': ('bar', 10.0)} 

612 

613 >>> np.safe_eval('import os') 

614 Traceback (most recent call last): 

615 ... 

616 SyntaxError: invalid syntax 

617 

618 >>> np.safe_eval('open("/home/user/.ssh/id_dsa").read()') 

619 Traceback (most recent call last): 

620 ... 

621 ValueError: malformed node or string: <_ast.Call object at 0x...> 

622 

623 """ 

624 

625 # Deprecated in NumPy 2.0, 2023-07-11 

626 warnings.warn( 

627 "`safe_eval` is deprecated. Use `ast.literal_eval` instead. " 

628 "Be aware of security implications, such as memory exhaustion " 

629 "based attacks (deprecated in NumPy 2.0)", 

630 DeprecationWarning, 

631 stacklevel=2 

632 ) 

633 

634 # Local import to speed up numpy's import time. 

635 import ast 

636 return ast.literal_eval(source) 

637 

638 

639def _median_nancheck(data, result, axis): 

640 """ 

641 Utility function to check median result from data for NaN values at the end 

642 and return NaN in that case. Input result can also be a MaskedArray. 

643 

644 Parameters 

645 ---------- 

646 data : array 

647 Sorted input data to median function 

648 result : Array or MaskedArray 

649 Result of median function. 

650 axis : int 

651 Axis along which the median was computed. 

652 

653 Returns 

654 ------- 

655 result : scalar or ndarray 

656 Median or NaN in axes which contained NaN in the input. If the input 

657 was an array, NaN will be inserted in-place. If a scalar, either the 

658 input itself or a scalar NaN. 

659 """ 

660 if data.size == 0: 

661 return result 

662 potential_nans = data.take(-1, axis=axis) 

663 n = np.isnan(potential_nans) 

664 # masked NaN values are ok, although for masked the copyto may fail for 

665 # unmasked ones (this was always broken) when the result is a scalar. 

666 if np.ma.isMaskedArray(n): 

667 n = n.filled(False) 

668 

669 if not n.any(): 

670 return result 

671 

672 # Without given output, it is possible that the current result is a 

673 # numpy scalar, which is not writeable. If so, just return nan. 

674 if isinstance(result, np.generic): 

675 return potential_nans 

676 

677 # Otherwise copy NaNs (if there are any) 

678 np.copyto(result, potential_nans, where=n) 

679 return result 

680 

681def _opt_info(): 

682 """ 

683 Returns a string containing the CPU features supported 

684 by the current build. 

685 

686 The format of the string can be explained as follows: 

687 - Dispatched features supported by the running machine end with `*`. 

688 - Dispatched features not supported by the running machine 

689 end with `?`. 

690 - Remaining features represent the baseline. 

691 

692 Returns: 

693 str: A formatted string indicating the supported CPU features. 

694 """ 

695 from numpy._core._multiarray_umath import ( 

696 __cpu_features__, __cpu_baseline__, __cpu_dispatch__ 

697 ) 

698 

699 if len(__cpu_baseline__) == 0 and len(__cpu_dispatch__) == 0: 

700 return '' 

701 

702 enabled_features = ' '.join(__cpu_baseline__) 

703 for feature in __cpu_dispatch__: 

704 if __cpu_features__[feature]: 

705 enabled_features += f" {feature}*" 

706 else: 

707 enabled_features += f" {feature}?" 

708 

709 return enabled_features 

710 

711def drop_metadata(dtype, /): 

712 """ 

713 Returns the dtype unchanged if it contained no metadata or a copy of the 

714 dtype if it (or any of its structure dtypes) contained metadata. 

715 

716 This utility is used by `np.save` and `np.savez` to drop metadata before 

717 saving. 

718 

719 .. note:: 

720 

721 Due to its limitation this function may move to a more appropriate 

722 home or change in the future and is considered semi-public API only. 

723 

724 .. warning:: 

725 

726 This function does not preserve more strange things like record dtypes 

727 and user dtypes may simply return the wrong thing. If you need to be 

728 sure about the latter, check the result with: 

729 ``np.can_cast(new_dtype, dtype, casting="no")``. 

730 

731 """ 

732 if dtype.fields is not None: 

733 found_metadata = dtype.metadata is not None 

734 

735 names = [] 

736 formats = [] 

737 offsets = [] 

738 titles = [] 

739 for name, field in dtype.fields.items(): 

740 field_dt = drop_metadata(field[0]) 

741 if field_dt is not field[0]: 

742 found_metadata = True 

743 

744 names.append(name) 

745 formats.append(field_dt) 

746 offsets.append(field[1]) 

747 titles.append(None if len(field) < 3 else field[2]) 

748 

749 if not found_metadata: 

750 return dtype 

751 

752 structure = dict( 

753 names=names, formats=formats, offsets=offsets, titles=titles, 

754 itemsize=dtype.itemsize) 

755 

756 # NOTE: Could pass (dtype.type, structure) to preserve record dtypes... 

757 return np.dtype(structure, align=dtype.isalignedstruct) 

758 elif dtype.subdtype is not None: 

759 # subarray dtype 

760 subdtype, shape = dtype.subdtype 

761 new_subdtype = drop_metadata(subdtype) 

762 if dtype.metadata is None and new_subdtype is subdtype: 

763 return dtype 

764 

765 return np.dtype((new_subdtype, shape)) 

766 else: 

767 # Normal unstructured dtype 

768 if dtype.metadata is None: 

769 return dtype 

770 # Note that `dt.str` doesn't round-trip e.g. for user-dtypes. 

771 return np.dtype(dtype.str)