_utils_impl.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. import os
  2. import sys
  3. import textwrap
  4. import types
  5. import warnings
  6. import functools
  7. import platform
  8. from numpy._core import ndarray
  9. from numpy._utils import set_module
  10. import numpy as np
  11. __all__ = [
  12. 'get_include', 'info', 'show_runtime'
  13. ]
  14. @set_module('numpy')
  15. def show_runtime():
  16. """
  17. Print information about various resources in the system
  18. including available intrinsic support and BLAS/LAPACK library
  19. in use
  20. .. versionadded:: 1.24.0
  21. See Also
  22. --------
  23. show_config : Show libraries in the system on which NumPy was built.
  24. Notes
  25. -----
  26. 1. Information is derived with the help of `threadpoolctl <https://pypi.org/project/threadpoolctl/>`_
  27. library if available.
  28. 2. SIMD related information is derived from ``__cpu_features__``,
  29. ``__cpu_baseline__`` and ``__cpu_dispatch__``
  30. """
  31. from numpy._core._multiarray_umath import (
  32. __cpu_features__, __cpu_baseline__, __cpu_dispatch__
  33. )
  34. from pprint import pprint
  35. config_found = [{
  36. "numpy_version": np.__version__,
  37. "python": sys.version,
  38. "uname": platform.uname(),
  39. }]
  40. features_found, features_not_found = [], []
  41. for feature in __cpu_dispatch__:
  42. if __cpu_features__[feature]:
  43. features_found.append(feature)
  44. else:
  45. features_not_found.append(feature)
  46. config_found.append({
  47. "simd_extensions": {
  48. "baseline": __cpu_baseline__,
  49. "found": features_found,
  50. "not_found": features_not_found
  51. }
  52. })
  53. try:
  54. from threadpoolctl import threadpool_info
  55. config_found.extend(threadpool_info())
  56. except ImportError:
  57. print("WARNING: `threadpoolctl` not found in system!"
  58. " Install it by `pip install threadpoolctl`."
  59. " Once installed, try `np.show_runtime` again"
  60. " for more detailed build information")
  61. pprint(config_found)
  62. @set_module('numpy')
  63. def get_include():
  64. """
  65. Return the directory that contains the NumPy \\*.h header files.
  66. Extension modules that need to compile against NumPy may need to use this
  67. function to locate the appropriate include directory.
  68. Notes
  69. -----
  70. When using ``setuptools``, for example in ``setup.py``::
  71. import numpy as np
  72. ...
  73. Extension('extension_name', ...
  74. include_dirs=[np.get_include()])
  75. ...
  76. Note that a CLI tool ``numpy-config`` was introduced in NumPy 2.0, using
  77. that is likely preferred for build systems other than ``setuptools``::
  78. $ numpy-config --cflags
  79. -I/path/to/site-packages/numpy/_core/include
  80. # Or rely on pkg-config:
  81. $ export PKG_CONFIG_PATH=$(numpy-config --pkgconfigdir)
  82. $ pkg-config --cflags
  83. -I/path/to/site-packages/numpy/_core/include
  84. Examples
  85. --------
  86. >>> np.get_include()
  87. '.../site-packages/numpy/core/include' # may vary
  88. """
  89. import numpy
  90. if numpy.show_config is None:
  91. # running from numpy source directory
  92. d = os.path.join(os.path.dirname(numpy.__file__), '_core', 'include')
  93. else:
  94. # using installed numpy core headers
  95. import numpy._core as _core
  96. d = os.path.join(os.path.dirname(_core.__file__), 'include')
  97. return d
  98. class _Deprecate:
  99. """
  100. Decorator class to deprecate old functions.
  101. Refer to `deprecate` for details.
  102. See Also
  103. --------
  104. deprecate
  105. """
  106. def __init__(self, old_name=None, new_name=None, message=None):
  107. self.old_name = old_name
  108. self.new_name = new_name
  109. self.message = message
  110. def __call__(self, func, *args, **kwargs):
  111. """
  112. Decorator call. Refer to ``decorate``.
  113. """
  114. old_name = self.old_name
  115. new_name = self.new_name
  116. message = self.message
  117. if old_name is None:
  118. old_name = func.__name__
  119. if new_name is None:
  120. depdoc = "`%s` is deprecated!" % old_name
  121. else:
  122. depdoc = "`%s` is deprecated, use `%s` instead!" % \
  123. (old_name, new_name)
  124. if message is not None:
  125. depdoc += "\n" + message
  126. @functools.wraps(func)
  127. def newfunc(*args, **kwds):
  128. warnings.warn(depdoc, DeprecationWarning, stacklevel=2)
  129. return func(*args, **kwds)
  130. newfunc.__name__ = old_name
  131. doc = func.__doc__
  132. if doc is None:
  133. doc = depdoc
  134. else:
  135. lines = doc.expandtabs().split('\n')
  136. indent = _get_indent(lines[1:])
  137. if lines[0].lstrip():
  138. # Indent the original first line to let inspect.cleandoc()
  139. # dedent the docstring despite the deprecation notice.
  140. doc = indent * ' ' + doc
  141. else:
  142. # Remove the same leading blank lines as cleandoc() would.
  143. skip = len(lines[0]) + 1
  144. for line in lines[1:]:
  145. if len(line) > indent:
  146. break
  147. skip += len(line) + 1
  148. doc = doc[skip:]
  149. depdoc = textwrap.indent(depdoc, ' ' * indent)
  150. doc = f'{depdoc}\n\n{doc}'
  151. newfunc.__doc__ = doc
  152. return newfunc
  153. def _get_indent(lines):
  154. """
  155. Determines the leading whitespace that could be removed from all the lines.
  156. """
  157. indent = sys.maxsize
  158. for line in lines:
  159. content = len(line.lstrip())
  160. if content:
  161. indent = min(indent, len(line) - content)
  162. if indent == sys.maxsize:
  163. indent = 0
  164. return indent
  165. def deprecate(*args, **kwargs):
  166. """
  167. Issues a DeprecationWarning, adds warning to `old_name`'s
  168. docstring, rebinds ``old_name.__name__`` and returns the new
  169. function object.
  170. This function may also be used as a decorator.
  171. .. deprecated:: 2.0
  172. Use `~warnings.warn` with :exc:`DeprecationWarning` instead.
  173. Parameters
  174. ----------
  175. func : function
  176. The function to be deprecated.
  177. old_name : str, optional
  178. The name of the function to be deprecated. Default is None, in
  179. which case the name of `func` is used.
  180. new_name : str, optional
  181. The new name for the function. Default is None, in which case the
  182. deprecation message is that `old_name` is deprecated. If given, the
  183. deprecation message is that `old_name` is deprecated and `new_name`
  184. should be used instead.
  185. message : str, optional
  186. Additional explanation of the deprecation. Displayed in the
  187. docstring after the warning.
  188. Returns
  189. -------
  190. old_func : function
  191. The deprecated function.
  192. Examples
  193. --------
  194. Note that ``olduint`` returns a value after printing Deprecation
  195. Warning:
  196. >>> olduint = np.lib.utils.deprecate(np.uint)
  197. DeprecationWarning: `uint64` is deprecated! # may vary
  198. >>> olduint(6)
  199. 6
  200. """
  201. # Deprecate may be run as a function or as a decorator
  202. # If run as a function, we initialise the decorator class
  203. # and execute its __call__ method.
  204. # Deprecated in NumPy 2.0, 2023-07-11
  205. warnings.warn(
  206. "`deprecate` is deprecated, "
  207. "use `warn` with `DeprecationWarning` instead. "
  208. "(deprecated in NumPy 2.0)",
  209. DeprecationWarning,
  210. stacklevel=2
  211. )
  212. if args:
  213. fn = args[0]
  214. args = args[1:]
  215. return _Deprecate(*args, **kwargs)(fn)
  216. else:
  217. return _Deprecate(*args, **kwargs)
  218. def deprecate_with_doc(msg):
  219. """
  220. Deprecates a function and includes the deprecation in its docstring.
  221. .. deprecated:: 2.0
  222. Use `~warnings.warn` with :exc:`DeprecationWarning` instead.
  223. This function is used as a decorator. It returns an object that can be
  224. used to issue a DeprecationWarning, by passing the to-be decorated
  225. function as argument, this adds warning to the to-be decorated function's
  226. docstring and returns the new function object.
  227. See Also
  228. --------
  229. deprecate : Decorate a function such that it issues a
  230. :exc:`DeprecationWarning`
  231. Parameters
  232. ----------
  233. msg : str
  234. Additional explanation of the deprecation. Displayed in the
  235. docstring after the warning.
  236. Returns
  237. -------
  238. obj : object
  239. """
  240. # Deprecated in NumPy 2.0, 2023-07-11
  241. warnings.warn(
  242. "`deprecate` is deprecated, "
  243. "use `warn` with `DeprecationWarning` instead. "
  244. "(deprecated in NumPy 2.0)",
  245. DeprecationWarning,
  246. stacklevel=2
  247. )
  248. return _Deprecate(message=msg)
  249. #-----------------------------------------------------------------------------
  250. # NOTE: pydoc defines a help function which works similarly to this
  251. # except it uses a pager to take over the screen.
  252. # combine name and arguments and split to multiple lines of width
  253. # characters. End lines on a comma and begin argument list indented with
  254. # the rest of the arguments.
  255. def _split_line(name, arguments, width):
  256. firstwidth = len(name)
  257. k = firstwidth
  258. newstr = name
  259. sepstr = ", "
  260. arglist = arguments.split(sepstr)
  261. for argument in arglist:
  262. if k == firstwidth:
  263. addstr = ""
  264. else:
  265. addstr = sepstr
  266. k = k + len(argument) + len(addstr)
  267. if k > width:
  268. k = firstwidth + 1 + len(argument)
  269. newstr = newstr + ",\n" + " "*(firstwidth+2) + argument
  270. else:
  271. newstr = newstr + addstr + argument
  272. return newstr
  273. _namedict = None
  274. _dictlist = None
  275. # Traverse all module directories underneath globals
  276. # to see if something is defined
  277. def _makenamedict(module='numpy'):
  278. module = __import__(module, globals(), locals(), [])
  279. thedict = {module.__name__:module.__dict__}
  280. dictlist = [module.__name__]
  281. totraverse = [module.__dict__]
  282. while True:
  283. if len(totraverse) == 0:
  284. break
  285. thisdict = totraverse.pop(0)
  286. for x in thisdict.keys():
  287. if isinstance(thisdict[x], types.ModuleType):
  288. modname = thisdict[x].__name__
  289. if modname not in dictlist:
  290. moddict = thisdict[x].__dict__
  291. dictlist.append(modname)
  292. totraverse.append(moddict)
  293. thedict[modname] = moddict
  294. return thedict, dictlist
  295. def _info(obj, output=None):
  296. """Provide information about ndarray obj.
  297. Parameters
  298. ----------
  299. obj : ndarray
  300. Must be ndarray, not checked.
  301. output
  302. Where printed output goes.
  303. Notes
  304. -----
  305. Copied over from the numarray module prior to its removal.
  306. Adapted somewhat as only numpy is an option now.
  307. Called by info.
  308. """
  309. extra = ""
  310. tic = ""
  311. bp = lambda x: x
  312. cls = getattr(obj, '__class__', type(obj))
  313. nm = getattr(cls, '__name__', cls)
  314. strides = obj.strides
  315. endian = obj.dtype.byteorder
  316. if output is None:
  317. output = sys.stdout
  318. print("class: ", nm, file=output)
  319. print("shape: ", obj.shape, file=output)
  320. print("strides: ", strides, file=output)
  321. print("itemsize: ", obj.itemsize, file=output)
  322. print("aligned: ", bp(obj.flags.aligned), file=output)
  323. print("contiguous: ", bp(obj.flags.contiguous), file=output)
  324. print("fortran: ", obj.flags.fortran, file=output)
  325. print(
  326. "data pointer: %s%s" % (hex(obj.ctypes._as_parameter_.value), extra),
  327. file=output
  328. )
  329. print("byteorder: ", end=' ', file=output)
  330. if endian in ['|', '=']:
  331. print("%s%s%s" % (tic, sys.byteorder, tic), file=output)
  332. byteswap = False
  333. elif endian == '>':
  334. print("%sbig%s" % (tic, tic), file=output)
  335. byteswap = sys.byteorder != "big"
  336. else:
  337. print("%slittle%s" % (tic, tic), file=output)
  338. byteswap = sys.byteorder != "little"
  339. print("byteswap: ", bp(byteswap), file=output)
  340. print("type: %s" % obj.dtype, file=output)
  341. @set_module('numpy')
  342. def info(object=None, maxwidth=76, output=None, toplevel='numpy'):
  343. """
  344. Get help information for an array, function, class, or module.
  345. Parameters
  346. ----------
  347. object : object or str, optional
  348. Input object or name to get information about. If `object` is
  349. an `ndarray` instance, information about the array is printed.
  350. If `object` is a numpy object, its docstring is given. If it is
  351. a string, available modules are searched for matching objects.
  352. If None, information about `info` itself is returned.
  353. maxwidth : int, optional
  354. Printing width.
  355. output : file like object, optional
  356. File like object that the output is written to, default is
  357. ``None``, in which case ``sys.stdout`` will be used.
  358. The object has to be opened in 'w' or 'a' mode.
  359. toplevel : str, optional
  360. Start search at this level.
  361. Notes
  362. -----
  363. When used interactively with an object, ``np.info(obj)`` is equivalent
  364. to ``help(obj)`` on the Python prompt or ``obj?`` on the IPython
  365. prompt.
  366. Examples
  367. --------
  368. >>> np.info(np.polyval) # doctest: +SKIP
  369. polyval(p, x)
  370. Evaluate the polynomial p at x.
  371. ...
  372. When using a string for `object` it is possible to get multiple results.
  373. >>> np.info('fft') # doctest: +SKIP
  374. *** Found in numpy ***
  375. Core FFT routines
  376. ...
  377. *** Found in numpy.fft ***
  378. fft(a, n=None, axis=-1)
  379. ...
  380. *** Repeat reference found in numpy.fft.fftpack ***
  381. *** Total of 3 references found. ***
  382. When the argument is an array, information about the array is printed.
  383. >>> a = np.array([[1 + 2j, 3, -4], [-5j, 6, 0]], dtype=np.complex64)
  384. >>> np.info(a)
  385. class: ndarray
  386. shape: (2, 3)
  387. strides: (24, 8)
  388. itemsize: 8
  389. aligned: True
  390. contiguous: True
  391. fortran: False
  392. data pointer: 0x562b6e0d2860 # may vary
  393. byteorder: little
  394. byteswap: False
  395. type: complex64
  396. """
  397. global _namedict, _dictlist
  398. # Local import to speed up numpy's import time.
  399. import pydoc
  400. import inspect
  401. if (hasattr(object, '_ppimport_importer') or
  402. hasattr(object, '_ppimport_module')):
  403. object = object._ppimport_module
  404. elif hasattr(object, '_ppimport_attr'):
  405. object = object._ppimport_attr
  406. if output is None:
  407. output = sys.stdout
  408. if object is None:
  409. info(info)
  410. elif isinstance(object, ndarray):
  411. _info(object, output=output)
  412. elif isinstance(object, str):
  413. if _namedict is None:
  414. _namedict, _dictlist = _makenamedict(toplevel)
  415. numfound = 0
  416. objlist = []
  417. for namestr in _dictlist:
  418. try:
  419. obj = _namedict[namestr][object]
  420. if id(obj) in objlist:
  421. print("\n "
  422. "*** Repeat reference found in %s *** " % namestr,
  423. file=output
  424. )
  425. else:
  426. objlist.append(id(obj))
  427. print(" *** Found in %s ***" % namestr, file=output)
  428. info(obj)
  429. print("-"*maxwidth, file=output)
  430. numfound += 1
  431. except KeyError:
  432. pass
  433. if numfound == 0:
  434. print("Help for %s not found." % object, file=output)
  435. else:
  436. print("\n "
  437. "*** Total of %d references found. ***" % numfound,
  438. file=output
  439. )
  440. elif inspect.isfunction(object) or inspect.ismethod(object):
  441. name = object.__name__
  442. try:
  443. arguments = str(inspect.signature(object))
  444. except Exception:
  445. arguments = "()"
  446. if len(name+arguments) > maxwidth:
  447. argstr = _split_line(name, arguments, maxwidth)
  448. else:
  449. argstr = name + arguments
  450. print(" " + argstr + "\n", file=output)
  451. print(inspect.getdoc(object), file=output)
  452. elif inspect.isclass(object):
  453. name = object.__name__
  454. try:
  455. arguments = str(inspect.signature(object))
  456. except Exception:
  457. arguments = "()"
  458. if len(name+arguments) > maxwidth:
  459. argstr = _split_line(name, arguments, maxwidth)
  460. else:
  461. argstr = name + arguments
  462. print(" " + argstr + "\n", file=output)
  463. doc1 = inspect.getdoc(object)
  464. if doc1 is None:
  465. if hasattr(object, '__init__'):
  466. print(inspect.getdoc(object.__init__), file=output)
  467. else:
  468. print(inspect.getdoc(object), file=output)
  469. methods = pydoc.allmethods(object)
  470. public_methods = [meth for meth in methods if meth[0] != '_']
  471. if public_methods:
  472. print("\n\nMethods:\n", file=output)
  473. for meth in public_methods:
  474. thisobj = getattr(object, meth, None)
  475. if thisobj is not None:
  476. methstr, other = pydoc.splitdoc(
  477. inspect.getdoc(thisobj) or "None"
  478. )
  479. print(" %s -- %s" % (meth, methstr), file=output)
  480. elif hasattr(object, '__doc__'):
  481. print(inspect.getdoc(object), file=output)
  482. def safe_eval(source):
  483. """
  484. Protected string evaluation.
  485. .. deprecated:: 2.0
  486. Use `ast.literal_eval` instead.
  487. Evaluate a string containing a Python literal expression without
  488. allowing the execution of arbitrary non-literal code.
  489. .. warning::
  490. This function is identical to :py:meth:`ast.literal_eval` and
  491. has the same security implications. It may not always be safe
  492. to evaluate large input strings.
  493. Parameters
  494. ----------
  495. source : str
  496. The string to evaluate.
  497. Returns
  498. -------
  499. obj : object
  500. The result of evaluating `source`.
  501. Raises
  502. ------
  503. SyntaxError
  504. If the code has invalid Python syntax, or if it contains
  505. non-literal code.
  506. Examples
  507. --------
  508. >>> np.safe_eval('1')
  509. 1
  510. >>> np.safe_eval('[1, 2, 3]')
  511. [1, 2, 3]
  512. >>> np.safe_eval('{"foo": ("bar", 10.0)}')
  513. {'foo': ('bar', 10.0)}
  514. >>> np.safe_eval('import os')
  515. Traceback (most recent call last):
  516. ...
  517. SyntaxError: invalid syntax
  518. >>> np.safe_eval('open("/home/user/.ssh/id_dsa").read()')
  519. Traceback (most recent call last):
  520. ...
  521. ValueError: malformed node or string: <_ast.Call object at 0x...>
  522. """
  523. # Deprecated in NumPy 2.0, 2023-07-11
  524. warnings.warn(
  525. "`safe_eval` is deprecated. Use `ast.literal_eval` instead. "
  526. "Be aware of security implications, such as memory exhaustion "
  527. "based attacks (deprecated in NumPy 2.0)",
  528. DeprecationWarning,
  529. stacklevel=2
  530. )
  531. # Local import to speed up numpy's import time.
  532. import ast
  533. return ast.literal_eval(source)
  534. def _median_nancheck(data, result, axis):
  535. """
  536. Utility function to check median result from data for NaN values at the end
  537. and return NaN in that case. Input result can also be a MaskedArray.
  538. Parameters
  539. ----------
  540. data : array
  541. Sorted input data to median function
  542. result : Array or MaskedArray
  543. Result of median function.
  544. axis : int
  545. Axis along which the median was computed.
  546. Returns
  547. -------
  548. result : scalar or ndarray
  549. Median or NaN in axes which contained NaN in the input. If the input
  550. was an array, NaN will be inserted in-place. If a scalar, either the
  551. input itself or a scalar NaN.
  552. """
  553. if data.size == 0:
  554. return result
  555. potential_nans = data.take(-1, axis=axis)
  556. n = np.isnan(potential_nans)
  557. # masked NaN values are ok, although for masked the copyto may fail for
  558. # unmasked ones (this was always broken) when the result is a scalar.
  559. if np.ma.isMaskedArray(n):
  560. n = n.filled(False)
  561. if not n.any():
  562. return result
  563. # Without given output, it is possible that the current result is a
  564. # numpy scalar, which is not writeable. If so, just return nan.
  565. if isinstance(result, np.generic):
  566. return potential_nans
  567. # Otherwise copy NaNs (if there are any)
  568. np.copyto(result, potential_nans, where=n)
  569. return result
  570. def _opt_info():
  571. """
  572. Returns a string containing the CPU features supported
  573. by the current build.
  574. The format of the string can be explained as follows:
  575. - Dispatched features supported by the running machine end with `*`.
  576. - Dispatched features not supported by the running machine
  577. end with `?`.
  578. - Remaining features represent the baseline.
  579. Returns:
  580. str: A formatted string indicating the supported CPU features.
  581. """
  582. from numpy._core._multiarray_umath import (
  583. __cpu_features__, __cpu_baseline__, __cpu_dispatch__
  584. )
  585. if len(__cpu_baseline__) == 0 and len(__cpu_dispatch__) == 0:
  586. return ''
  587. enabled_features = ' '.join(__cpu_baseline__)
  588. for feature in __cpu_dispatch__:
  589. if __cpu_features__[feature]:
  590. enabled_features += f" {feature}*"
  591. else:
  592. enabled_features += f" {feature}?"
  593. return enabled_features
  594. def drop_metadata(dtype, /):
  595. """
  596. Returns the dtype unchanged if it contained no metadata or a copy of the
  597. dtype if it (or any of its structure dtypes) contained metadata.
  598. This utility is used by `np.save` and `np.savez` to drop metadata before
  599. saving.
  600. .. note::
  601. Due to its limitation this function may move to a more appropriate
  602. home or change in the future and is considered semi-public API only.
  603. .. warning::
  604. This function does not preserve more strange things like record dtypes
  605. and user dtypes may simply return the wrong thing. If you need to be
  606. sure about the latter, check the result with:
  607. ``np.can_cast(new_dtype, dtype, casting="no")``.
  608. """
  609. if dtype.fields is not None:
  610. found_metadata = dtype.metadata is not None
  611. names = []
  612. formats = []
  613. offsets = []
  614. titles = []
  615. for name, field in dtype.fields.items():
  616. field_dt = drop_metadata(field[0])
  617. if field_dt is not field[0]:
  618. found_metadata = True
  619. names.append(name)
  620. formats.append(field_dt)
  621. offsets.append(field[1])
  622. titles.append(None if len(field) < 3 else field[2])
  623. if not found_metadata:
  624. return dtype
  625. structure = dict(
  626. names=names, formats=formats, offsets=offsets, titles=titles,
  627. itemsize=dtype.itemsize)
  628. # NOTE: Could pass (dtype.type, structure) to preserve record dtypes...
  629. return np.dtype(structure, align=dtype.isalignedstruct)
  630. elif dtype.subdtype is not None:
  631. # subarray dtype
  632. subdtype, shape = dtype.subdtype
  633. new_subdtype = drop_metadata(subdtype)
  634. if dtype.metadata is None and new_subdtype is subdtype:
  635. return dtype
  636. return np.dtype((new_subdtype, shape))
  637. else:
  638. # Normal unstructured dtype
  639. if dtype.metadata is None:
  640. return dtype
  641. # Note that `dt.str` doesn't round-trip e.g. for user-dtypes.
  642. return np.dtype(dtype.str)