Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/numpy/lib/_utils_impl.py: 11%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

284 statements  

1import os 

2import sys 

3import textwrap 

4import types 

5import warnings 

6import functools 

7import platform 

8 

9from numpy._core import ndarray 

10from numpy._utils import set_module 

11import numpy as np 

12 

13__all__ = [ 

14 'get_include', 'info', 'show_runtime' 

15] 

16 

17 

18@set_module('numpy') 

19def show_runtime(): 

20 """ 

21 Print information about various resources in the system 

22 including available intrinsic support and BLAS/LAPACK library 

23 in use 

24 

25 .. versionadded:: 1.24.0 

26 

27 See Also 

28 -------- 

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

30 

31 Notes 

32 ----- 

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

34 library if available. 

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

36 ``__cpu_baseline__`` and ``__cpu_dispatch__`` 

37 

38 """ 

39 from numpy._core._multiarray_umath import ( 

40 __cpu_features__, __cpu_baseline__, __cpu_dispatch__ 

41 ) 

42 from pprint import pprint 

43 config_found = [{ 

44 "numpy_version": np.__version__, 

45 "python": sys.version, 

46 "uname": platform.uname(), 

47 }] 

48 features_found, features_not_found = [], [] 

49 for feature in __cpu_dispatch__: 

50 if __cpu_features__[feature]: 

51 features_found.append(feature) 

52 else: 

53 features_not_found.append(feature) 

54 config_found.append({ 

55 "simd_extensions": { 

56 "baseline": __cpu_baseline__, 

57 "found": features_found, 

58 "not_found": features_not_found 

59 } 

60 }) 

61 try: 

62 from threadpoolctl import threadpool_info 

63 config_found.extend(threadpool_info()) 

64 except ImportError: 

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

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

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

68 " for more detailed build information") 

69 pprint(config_found) 

70 

71 

72@set_module('numpy') 

73def get_include(): 

74 """ 

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

76 

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

78 function to locate the appropriate include directory. 

79 

80 Notes 

81 ----- 

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

83 

84 import numpy as np 

85 ... 

86 Extension('extension_name', ... 

87 include_dirs=[np.get_include()]) 

88 ... 

89 

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

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

92 

93 $ numpy-config --cflags 

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

95 

96 # Or rely on pkg-config: 

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

98 $ pkg-config --cflags 

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

100 

101 Examples 

102 -------- 

103 >>> np.get_include() 

104 '.../site-packages/numpy/core/include' # may vary 

105 

106 """ 

107 import numpy 

108 if numpy.show_config is None: 

109 # running from numpy source directory 

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

111 else: 

112 # using installed numpy core headers 

113 import numpy._core as _core 

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

115 return d 

116 

117 

118class _Deprecate: 

119 """ 

120 Decorator class to deprecate old functions. 

121 

122 Refer to `deprecate` for details. 

123 

124 See Also 

125 -------- 

126 deprecate 

127 

128 """ 

129 

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

131 self.old_name = old_name 

132 self.new_name = new_name 

133 self.message = message 

134 

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

136 """ 

137 Decorator call. Refer to ``decorate``. 

138 

139 """ 

140 old_name = self.old_name 

141 new_name = self.new_name 

142 message = self.message 

143 

144 if old_name is None: 

145 old_name = func.__name__ 

146 if new_name is None: 

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

148 else: 

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

150 (old_name, new_name) 

151 

152 if message is not None: 

153 depdoc += "\n" + message 

154 

155 @functools.wraps(func) 

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

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

158 return func(*args, **kwds) 

159 

160 newfunc.__name__ = old_name 

161 doc = func.__doc__ 

162 if doc is None: 

163 doc = depdoc 

164 else: 

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

166 indent = _get_indent(lines[1:]) 

167 if lines[0].lstrip(): 

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

169 # dedent the docstring despite the deprecation notice. 

170 doc = indent * ' ' + doc 

171 else: 

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

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

174 for line in lines[1:]: 

175 if len(line) > indent: 

176 break 

177 skip += len(line) + 1 

178 doc = doc[skip:] 

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

180 doc = f'{depdoc}\n\n{doc}' 

181 newfunc.__doc__ = doc 

182 

183 return newfunc 

184 

185 

186def _get_indent(lines): 

187 """ 

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

189 """ 

190 indent = sys.maxsize 

191 for line in lines: 

192 content = len(line.lstrip()) 

193 if content: 

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

195 if indent == sys.maxsize: 

196 indent = 0 

197 return indent 

198 

199 

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

201 """ 

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

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

204 function object. 

205 

206 This function may also be used as a decorator. 

207 

208 .. deprecated:: 2.0 

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

210 

211 Parameters 

212 ---------- 

213 func : function 

214 The function to be deprecated. 

215 old_name : str, optional 

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

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

218 new_name : str, optional 

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

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

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

222 should be used instead. 

223 message : str, optional 

224 Additional explanation of the deprecation. Displayed in the 

225 docstring after the warning. 

226 

227 Returns 

228 ------- 

229 old_func : function 

230 The deprecated function. 

231 

232 Examples 

233 -------- 

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

235 Warning: 

236 

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

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

239 >>> olduint(6) 

240 6 

241 

242 """ 

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

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

245 # and execute its __call__ method. 

246 

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

248 warnings.warn( 

249 "`deprecate` is deprecated, " 

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

251 "(deprecated in NumPy 2.0)", 

252 DeprecationWarning, 

253 stacklevel=2 

254 ) 

255 

256 if args: 

257 fn = args[0] 

258 args = args[1:] 

259 

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

261 else: 

262 return _Deprecate(*args, **kwargs) 

263 

264 

265def deprecate_with_doc(msg): 

266 """ 

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

268 

269 .. deprecated:: 2.0 

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

271 

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

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

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

275 docstring and returns the new function object. 

276 

277 See Also 

278 -------- 

279 deprecate : Decorate a function such that it issues a 

280 :exc:`DeprecationWarning` 

281 

282 Parameters 

283 ---------- 

284 msg : str 

285 Additional explanation of the deprecation. Displayed in the 

286 docstring after the warning. 

287 

288 Returns 

289 ------- 

290 obj : object 

291 

292 """ 

293 

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

295 warnings.warn( 

296 "`deprecate` is deprecated, " 

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

298 "(deprecated in NumPy 2.0)", 

299 DeprecationWarning, 

300 stacklevel=2 

301 ) 

302 

303 return _Deprecate(message=msg) 

304 

305 

306#----------------------------------------------------------------------------- 

307 

308 

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

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

311 

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

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

314# the rest of the arguments. 

315def _split_line(name, arguments, width): 

316 firstwidth = len(name) 

317 k = firstwidth 

318 newstr = name 

319 sepstr = ", " 

320 arglist = arguments.split(sepstr) 

321 for argument in arglist: 

322 if k == firstwidth: 

323 addstr = "" 

324 else: 

325 addstr = sepstr 

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

327 if k > width: 

328 k = firstwidth + 1 + len(argument) 

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

330 else: 

331 newstr = newstr + addstr + argument 

332 return newstr 

333 

334_namedict = None 

335_dictlist = None 

336 

337# Traverse all module directories underneath globals 

338# to see if something is defined 

339def _makenamedict(module='numpy'): 

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

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

342 dictlist = [module.__name__] 

343 totraverse = [module.__dict__] 

344 while True: 

345 if len(totraverse) == 0: 

346 break 

347 thisdict = totraverse.pop(0) 

348 for x in thisdict.keys(): 

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

350 modname = thisdict[x].__name__ 

351 if modname not in dictlist: 

352 moddict = thisdict[x].__dict__ 

353 dictlist.append(modname) 

354 totraverse.append(moddict) 

355 thedict[modname] = moddict 

356 return thedict, dictlist 

357 

358 

359def _info(obj, output=None): 

360 """Provide information about ndarray obj. 

361 

362 Parameters 

363 ---------- 

364 obj : ndarray 

365 Must be ndarray, not checked. 

366 output 

367 Where printed output goes. 

368 

369 Notes 

370 ----- 

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

372 Adapted somewhat as only numpy is an option now. 

373 

374 Called by info. 

375 

376 """ 

377 extra = "" 

378 tic = "" 

379 bp = lambda x: x 

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

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

382 strides = obj.strides 

383 endian = obj.dtype.byteorder 

384 

385 if output is None: 

386 output = sys.stdout 

387 

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

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

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

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

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

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

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

395 print( 

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

397 file=output 

398 ) 

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

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

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

402 byteswap = False 

403 elif endian == '>': 

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

405 byteswap = sys.byteorder != "big" 

406 else: 

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

408 byteswap = sys.byteorder != "little" 

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

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

411 

412 

413@set_module('numpy') 

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

415 """ 

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

417 

418 Parameters 

419 ---------- 

420 object : object or str, optional 

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

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

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

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

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

426 maxwidth : int, optional 

427 Printing width. 

428 output : file like object, optional 

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

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

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

432 toplevel : str, optional 

433 Start search at this level. 

434 

435 Notes 

436 ----- 

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

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

439 prompt. 

440 

441 Examples 

442 -------- 

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

444 polyval(p, x) 

445 Evaluate the polynomial p at x. 

446 ... 

447 

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

449 

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

451 *** Found in numpy *** 

452 Core FFT routines 

453 ... 

454 *** Found in numpy.fft *** 

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

456 ... 

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

458 *** Total of 3 references found. *** 

459 

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

461 

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

463 >>> np.info(a) 

464 class: ndarray 

465 shape: (2, 3) 

466 strides: (24, 8) 

467 itemsize: 8 

468 aligned: True 

469 contiguous: True 

470 fortran: False 

471 data pointer: 0x562b6e0d2860 # may vary 

472 byteorder: little 

473 byteswap: False 

474 type: complex64 

475 

476 """ 

477 global _namedict, _dictlist 

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

479 import pydoc 

480 import inspect 

481 

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

483 hasattr(object, '_ppimport_module')): 

484 object = object._ppimport_module 

485 elif hasattr(object, '_ppimport_attr'): 

486 object = object._ppimport_attr 

487 

488 if output is None: 

489 output = sys.stdout 

490 

491 if object is None: 

492 info(info) 

493 elif isinstance(object, ndarray): 

494 _info(object, output=output) 

495 elif isinstance(object, str): 

496 if _namedict is None: 

497 _namedict, _dictlist = _makenamedict(toplevel) 

498 numfound = 0 

499 objlist = [] 

500 for namestr in _dictlist: 

501 try: 

502 obj = _namedict[namestr][object] 

503 if id(obj) in objlist: 

504 print("\n " 

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

506 file=output 

507 ) 

508 else: 

509 objlist.append(id(obj)) 

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

511 info(obj) 

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

513 numfound += 1 

514 except KeyError: 

515 pass 

516 if numfound == 0: 

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

518 else: 

519 print("\n " 

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

521 file=output 

522 ) 

523 

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

525 name = object.__name__ 

526 try: 

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

528 except Exception: 

529 arguments = "()" 

530 

531 if len(name+arguments) > maxwidth: 

532 argstr = _split_line(name, arguments, maxwidth) 

533 else: 

534 argstr = name + arguments 

535 

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

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

538 

539 elif inspect.isclass(object): 

540 name = object.__name__ 

541 try: 

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

543 except Exception: 

544 arguments = "()" 

545 

546 if len(name+arguments) > maxwidth: 

547 argstr = _split_line(name, arguments, maxwidth) 

548 else: 

549 argstr = name + arguments 

550 

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

552 doc1 = inspect.getdoc(object) 

553 if doc1 is None: 

554 if hasattr(object, '__init__'): 

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

556 else: 

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

558 

559 methods = pydoc.allmethods(object) 

560 

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

562 if public_methods: 

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

564 for meth in public_methods: 

565 thisobj = getattr(object, meth, None) 

566 if thisobj is not None: 

567 methstr, other = pydoc.splitdoc( 

568 inspect.getdoc(thisobj) or "None" 

569 ) 

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

571 

572 elif hasattr(object, '__doc__'): 

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

574 

575 

576def safe_eval(source): 

577 """ 

578 Protected string evaluation. 

579 

580 .. deprecated:: 2.0 

581 Use `ast.literal_eval` instead. 

582 

583 Evaluate a string containing a Python literal expression without 

584 allowing the execution of arbitrary non-literal code. 

585 

586 .. warning:: 

587 

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

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

590 to evaluate large input strings. 

591 

592 Parameters 

593 ---------- 

594 source : str 

595 The string to evaluate. 

596 

597 Returns 

598 ------- 

599 obj : object 

600 The result of evaluating `source`. 

601 

602 Raises 

603 ------ 

604 SyntaxError 

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

606 non-literal code. 

607 

608 Examples 

609 -------- 

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

611 1 

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

613 [1, 2, 3] 

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

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

616 

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

618 Traceback (most recent call last): 

619 ... 

620 SyntaxError: invalid syntax 

621 

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

623 Traceback (most recent call last): 

624 ... 

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

626 

627 """ 

628 

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

630 warnings.warn( 

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

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

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

634 DeprecationWarning, 

635 stacklevel=2 

636 ) 

637 

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

639 import ast 

640 return ast.literal_eval(source) 

641 

642 

643def _median_nancheck(data, result, axis): 

644 """ 

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

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

647 

648 Parameters 

649 ---------- 

650 data : array 

651 Sorted input data to median function 

652 result : Array or MaskedArray 

653 Result of median function. 

654 axis : int 

655 Axis along which the median was computed. 

656 

657 Returns 

658 ------- 

659 result : scalar or ndarray 

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

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

662 input itself or a scalar NaN. 

663 """ 

664 if data.size == 0: 

665 return result 

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

667 n = np.isnan(potential_nans) 

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

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

670 if np.ma.isMaskedArray(n): 

671 n = n.filled(False) 

672 

673 if not n.any(): 

674 return result 

675 

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

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

678 if isinstance(result, np.generic): 

679 return potential_nans 

680 

681 # Otherwise copy NaNs (if there are any) 

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

683 return result 

684 

685def _opt_info(): 

686 """ 

687 Returns a string containing the CPU features supported 

688 by the current build. 

689 

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

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

692 - Dispatched features not supported by the running machine 

693 end with `?`. 

694 - Remaining features represent the baseline. 

695 

696 Returns: 

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

698 """ 

699 from numpy._core._multiarray_umath import ( 

700 __cpu_features__, __cpu_baseline__, __cpu_dispatch__ 

701 ) 

702 

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

704 return '' 

705 

706 enabled_features = ' '.join(__cpu_baseline__) 

707 for feature in __cpu_dispatch__: 

708 if __cpu_features__[feature]: 

709 enabled_features += f" {feature}*" 

710 else: 

711 enabled_features += f" {feature}?" 

712 

713 return enabled_features 

714 

715def drop_metadata(dtype, /): 

716 """ 

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

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

719 

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

721 saving. 

722 

723 .. note:: 

724 

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

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

727 

728 .. warning:: 

729 

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

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

732 sure about the latter, check the result with: 

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

734 

735 """ 

736 if dtype.fields is not None: 

737 found_metadata = dtype.metadata is not None 

738 

739 names = [] 

740 formats = [] 

741 offsets = [] 

742 titles = [] 

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

744 field_dt = drop_metadata(field[0]) 

745 if field_dt is not field[0]: 

746 found_metadata = True 

747 

748 names.append(name) 

749 formats.append(field_dt) 

750 offsets.append(field[1]) 

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

752 

753 if not found_metadata: 

754 return dtype 

755 

756 structure = dict( 

757 names=names, formats=formats, offsets=offsets, titles=titles, 

758 itemsize=dtype.itemsize) 

759 

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

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

762 elif dtype.subdtype is not None: 

763 # subarray dtype 

764 subdtype, shape = dtype.subdtype 

765 new_subdtype = drop_metadata(subdtype) 

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

767 return dtype 

768 

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

770 else: 

771 # Normal unstructured dtype 

772 if dtype.metadata is None: 

773 return dtype 

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

775 return np.dtype(dtype.str)