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