Coverage for /usr/lib/python3/dist-packages/sympy/utilities/decorator.py: 61%
121 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Useful utility decorators. """
3import sys
4import types
5import inspect
6from functools import wraps, update_wrapper
8from sympy.utilities.exceptions import sympy_deprecation_warning
10def threaded_factory(func, use_add):
11 """A factory for ``threaded`` decorators. """
12 from sympy.core import sympify
13 from sympy.matrices import MatrixBase
14 from sympy.utilities.iterables import iterable
16 @wraps(func)
17 def threaded_func(expr, *args, **kwargs):
18 if isinstance(expr, MatrixBase):
19 return expr.applyfunc(lambda f: func(f, *args, **kwargs))
20 elif iterable(expr):
21 try:
22 return expr.__class__([func(f, *args, **kwargs) for f in expr])
23 except TypeError:
24 return expr
25 else:
26 expr = sympify(expr)
28 if use_add and expr.is_Add:
29 return expr.__class__(*[ func(f, *args, **kwargs) for f in expr.args ])
30 elif expr.is_Relational:
31 return expr.__class__(func(expr.lhs, *args, **kwargs),
32 func(expr.rhs, *args, **kwargs))
33 else:
34 return func(expr, *args, **kwargs)
36 return threaded_func
39def threaded(func):
40 """Apply ``func`` to sub--elements of an object, including :class:`~.Add`.
42 This decorator is intended to make it uniformly possible to apply a
43 function to all elements of composite objects, e.g. matrices, lists, tuples
44 and other iterable containers, or just expressions.
46 This version of :func:`threaded` decorator allows threading over
47 elements of :class:`~.Add` class. If this behavior is not desirable
48 use :func:`xthreaded` decorator.
50 Functions using this decorator must have the following signature::
52 @threaded
53 def function(expr, *args, **kwargs):
55 """
56 return threaded_factory(func, True)
59def xthreaded(func):
60 """Apply ``func`` to sub--elements of an object, excluding :class:`~.Add`.
62 This decorator is intended to make it uniformly possible to apply a
63 function to all elements of composite objects, e.g. matrices, lists, tuples
64 and other iterable containers, or just expressions.
66 This version of :func:`threaded` decorator disallows threading over
67 elements of :class:`~.Add` class. If this behavior is not desirable
68 use :func:`threaded` decorator.
70 Functions using this decorator must have the following signature::
72 @xthreaded
73 def function(expr, *args, **kwargs):
75 """
76 return threaded_factory(func, False)
79def conserve_mpmath_dps(func):
80 """After the function finishes, resets the value of mpmath.mp.dps to
81 the value it had before the function was run."""
82 import mpmath
84 def func_wrapper(*args, **kwargs):
85 dps = mpmath.mp.dps
86 try:
87 return func(*args, **kwargs)
88 finally:
89 mpmath.mp.dps = dps
91 func_wrapper = update_wrapper(func_wrapper, func)
92 return func_wrapper
95class no_attrs_in_subclass:
96 """Don't 'inherit' certain attributes from a base class
98 >>> from sympy.utilities.decorator import no_attrs_in_subclass
100 >>> class A(object):
101 ... x = 'test'
103 >>> A.x = no_attrs_in_subclass(A, A.x)
105 >>> class B(A):
106 ... pass
108 >>> hasattr(A, 'x')
109 True
110 >>> hasattr(B, 'x')
111 False
113 """
114 def __init__(self, cls, f):
115 self.cls = cls
116 self.f = f
118 def __get__(self, instance, owner=None):
119 if owner == self.cls:
120 if hasattr(self.f, '__get__'):
121 return self.f.__get__(instance, owner)
122 return self.f
123 raise AttributeError
126def doctest_depends_on(exe=None, modules=None, disable_viewers=None, python_version=None):
127 """
128 Adds metadata about the dependencies which need to be met for doctesting
129 the docstrings of the decorated objects.
131 exe should be a list of executables
133 modules should be a list of modules
135 disable_viewers should be a list of viewers for preview() to disable
137 python_version should be the minimum Python version required, as a tuple
138 (like (3, 0))
139 """
140 dependencies = {}
141 if exe is not None:
142 dependencies['executables'] = exe
143 if modules is not None:
144 dependencies['modules'] = modules
145 if disable_viewers is not None:
146 dependencies['disable_viewers'] = disable_viewers
147 if python_version is not None:
148 dependencies['python_version'] = python_version
150 def skiptests():
151 from sympy.testing.runtests import DependencyError, SymPyDocTests, PyTestReporter # lazy import
152 r = PyTestReporter()
153 t = SymPyDocTests(r, None)
154 try:
155 t._check_dependencies(**dependencies)
156 except DependencyError:
157 return True # Skip doctests
158 else:
159 return False # Run doctests
161 def depends_on_deco(fn):
162 fn._doctest_depends_on = dependencies
163 fn.__doctest_skip__ = skiptests
165 if inspect.isclass(fn):
166 fn._doctest_depdends_on = no_attrs_in_subclass(
167 fn, fn._doctest_depends_on)
168 fn.__doctest_skip__ = no_attrs_in_subclass(
169 fn, fn.__doctest_skip__)
170 return fn
172 return depends_on_deco
175def public(obj):
176 """
177 Append ``obj``'s name to global ``__all__`` variable (call site).
179 By using this decorator on functions or classes you achieve the same goal
180 as by filling ``__all__`` variables manually, you just do not have to repeat
181 yourself (object's name). You also know if object is public at definition
182 site, not at some random location (where ``__all__`` was set).
184 Note that in multiple decorator setup (in almost all cases) ``@public``
185 decorator must be applied before any other decorators, because it relies
186 on the pointer to object's global namespace. If you apply other decorators
187 first, ``@public`` may end up modifying the wrong namespace.
189 Examples
190 ========
192 >>> from sympy.utilities.decorator import public
194 >>> __all__ # noqa: F821
195 Traceback (most recent call last):
196 ...
197 NameError: name '__all__' is not defined
199 >>> @public
200 ... def some_function():
201 ... pass
203 >>> __all__ # noqa: F821
204 ['some_function']
206 """
207 if isinstance(obj, types.FunctionType):
208 ns = obj.__globals__
209 name = obj.__name__
210 elif isinstance(obj, (type(type), type)):
211 ns = sys.modules[obj.__module__].__dict__
212 name = obj.__name__
213 else:
214 raise TypeError("expected a function or a class, got %s" % obj)
216 if "__all__" not in ns:
217 ns["__all__"] = [name]
218 else:
219 ns["__all__"].append(name)
221 return obj
224def memoize_property(propfunc):
225 """Property decorator that caches the value of potentially expensive
226 `propfunc` after the first evaluation. The cached value is stored in
227 the corresponding property name with an attached underscore."""
228 attrname = '_' + propfunc.__name__
229 sentinel = object()
231 @wraps(propfunc)
232 def accessor(self):
233 val = getattr(self, attrname, sentinel)
234 if val is sentinel:
235 val = propfunc(self)
236 setattr(self, attrname, val)
237 return val
239 return property(accessor)
242def deprecated(message, *, deprecated_since_version,
243 active_deprecations_target, stacklevel=3):
244 '''
245 Mark a function as deprecated.
247 This decorator should be used if an entire function or class is
248 deprecated. If only a certain functionality is deprecated, you should use
249 :func:`~.warns_deprecated_sympy` directly. This decorator is just a
250 convenience. There is no functional difference between using this
251 decorator and calling ``warns_deprecated_sympy()`` at the top of the
252 function.
254 The decorator takes the same arguments as
255 :func:`~.warns_deprecated_sympy`. See its
256 documentation for details on what the keywords to this decorator do.
258 See the :ref:`deprecation-policy` document for details on when and how
259 things should be deprecated in SymPy.
261 Examples
262 ========
264 >>> from sympy.utilities.decorator import deprecated
265 >>> from sympy import simplify
266 >>> @deprecated("""\
267 ... The simplify_this(expr) function is deprecated. Use simplify(expr)
268 ... instead.""", deprecated_since_version="1.1",
269 ... active_deprecations_target='simplify-this-deprecation')
270 ... def simplify_this(expr):
271 ... """
272 ... Simplify ``expr``.
273 ...
274 ... .. deprecated:: 1.1
275 ...
276 ... The ``simplify_this`` function is deprecated. Use :func:`simplify`
277 ... instead. See its documentation for more information. See
278 ... :ref:`simplify-this-deprecation` for details.
279 ...
280 ... """
281 ... return simplify(expr)
282 >>> from sympy.abc import x
283 >>> simplify_this(x*(x + 1) - x**2) # doctest: +SKIP
284 <stdin>:1: SymPyDeprecationWarning:
285 <BLANKLINE>
286 The simplify_this(expr) function is deprecated. Use simplify(expr)
287 instead.
288 <BLANKLINE>
289 See https://docs.sympy.org/latest/explanation/active-deprecations.html#simplify-this-deprecation
290 for details.
291 <BLANKLINE>
292 This has been deprecated since SymPy version 1.1. It
293 will be removed in a future version of SymPy.
294 <BLANKLINE>
295 simplify_this(x)
296 x
298 See Also
299 ========
300 sympy.utilities.exceptions.SymPyDeprecationWarning
301 sympy.utilities.exceptions.sympy_deprecation_warning
302 sympy.utilities.exceptions.ignore_warnings
303 sympy.testing.pytest.warns_deprecated_sympy
305 '''
306 decorator_kwargs = {"deprecated_since_version": deprecated_since_version,
307 "active_deprecations_target": active_deprecations_target}
308 def deprecated_decorator(wrapped):
309 if hasattr(wrapped, '__mro__'): # wrapped is actually a class
310 class wrapper(wrapped):
311 __doc__ = wrapped.__doc__
312 __module__ = wrapped.__module__
313 _sympy_deprecated_func = wrapped
314 if '__new__' in wrapped.__dict__:
315 def __new__(cls, *args, **kwargs):
316 sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel)
317 return super().__new__(cls, *args, **kwargs)
318 else:
319 def __init__(self, *args, **kwargs):
320 sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel)
321 super().__init__(*args, **kwargs)
322 wrapper.__name__ = wrapped.__name__
323 else:
324 @wraps(wrapped)
325 def wrapper(*args, **kwargs):
326 sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel)
327 return wrapped(*args, **kwargs)
328 wrapper._sympy_deprecated_func = wrapped
329 return wrapper
330 return deprecated_decorator