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)