|
|
"""Useful utility decorators. """ |
|
|
|
|
|
from typing import TypeVar |
|
|
import sys |
|
|
import types |
|
|
import inspect |
|
|
from functools import wraps, update_wrapper |
|
|
|
|
|
from sympy.utilities.exceptions import sympy_deprecation_warning |
|
|
|
|
|
|
|
|
T = TypeVar('T') |
|
|
"""A generic type""" |
|
|
|
|
|
|
|
|
def threaded_factory(func, use_add): |
|
|
"""A factory for ``threaded`` decorators. """ |
|
|
from sympy.core import sympify |
|
|
from sympy.matrices import MatrixBase |
|
|
from sympy.utilities.iterables import iterable |
|
|
|
|
|
@wraps(func) |
|
|
def threaded_func(expr, *args, **kwargs): |
|
|
if isinstance(expr, MatrixBase): |
|
|
return expr.applyfunc(lambda f: func(f, *args, **kwargs)) |
|
|
elif iterable(expr): |
|
|
try: |
|
|
return expr.__class__([func(f, *args, **kwargs) for f in expr]) |
|
|
except TypeError: |
|
|
return expr |
|
|
else: |
|
|
expr = sympify(expr) |
|
|
|
|
|
if use_add and expr.is_Add: |
|
|
return expr.__class__(*[ func(f, *args, **kwargs) for f in expr.args ]) |
|
|
elif expr.is_Relational: |
|
|
return expr.__class__(func(expr.lhs, *args, **kwargs), |
|
|
func(expr.rhs, *args, **kwargs)) |
|
|
else: |
|
|
return func(expr, *args, **kwargs) |
|
|
|
|
|
return threaded_func |
|
|
|
|
|
|
|
|
def threaded(func): |
|
|
"""Apply ``func`` to sub--elements of an object, including :class:`~.Add`. |
|
|
|
|
|
This decorator is intended to make it uniformly possible to apply a |
|
|
function to all elements of composite objects, e.g. matrices, lists, tuples |
|
|
and other iterable containers, or just expressions. |
|
|
|
|
|
This version of :func:`threaded` decorator allows threading over |
|
|
elements of :class:`~.Add` class. If this behavior is not desirable |
|
|
use :func:`xthreaded` decorator. |
|
|
|
|
|
Functions using this decorator must have the following signature:: |
|
|
|
|
|
@threaded |
|
|
def function(expr, *args, **kwargs): |
|
|
|
|
|
""" |
|
|
return threaded_factory(func, True) |
|
|
|
|
|
|
|
|
def xthreaded(func): |
|
|
"""Apply ``func`` to sub--elements of an object, excluding :class:`~.Add`. |
|
|
|
|
|
This decorator is intended to make it uniformly possible to apply a |
|
|
function to all elements of composite objects, e.g. matrices, lists, tuples |
|
|
and other iterable containers, or just expressions. |
|
|
|
|
|
This version of :func:`threaded` decorator disallows threading over |
|
|
elements of :class:`~.Add` class. If this behavior is not desirable |
|
|
use :func:`threaded` decorator. |
|
|
|
|
|
Functions using this decorator must have the following signature:: |
|
|
|
|
|
@xthreaded |
|
|
def function(expr, *args, **kwargs): |
|
|
|
|
|
""" |
|
|
return threaded_factory(func, False) |
|
|
|
|
|
|
|
|
def conserve_mpmath_dps(func): |
|
|
"""After the function finishes, resets the value of ``mpmath.mp.dps`` to |
|
|
the value it had before the function was run.""" |
|
|
import mpmath |
|
|
|
|
|
def func_wrapper(*args, **kwargs): |
|
|
dps = mpmath.mp.dps |
|
|
try: |
|
|
return func(*args, **kwargs) |
|
|
finally: |
|
|
mpmath.mp.dps = dps |
|
|
|
|
|
func_wrapper = update_wrapper(func_wrapper, func) |
|
|
return func_wrapper |
|
|
|
|
|
|
|
|
class no_attrs_in_subclass: |
|
|
"""Don't 'inherit' certain attributes from a base class |
|
|
|
|
|
>>> from sympy.utilities.decorator import no_attrs_in_subclass |
|
|
|
|
|
>>> class A(object): |
|
|
... x = 'test' |
|
|
|
|
|
>>> A.x = no_attrs_in_subclass(A, A.x) |
|
|
|
|
|
>>> class B(A): |
|
|
... pass |
|
|
|
|
|
>>> hasattr(A, 'x') |
|
|
True |
|
|
>>> hasattr(B, 'x') |
|
|
False |
|
|
|
|
|
""" |
|
|
def __init__(self, cls, f): |
|
|
self.cls = cls |
|
|
self.f = f |
|
|
|
|
|
def __get__(self, instance, owner=None): |
|
|
if owner == self.cls: |
|
|
if hasattr(self.f, '__get__'): |
|
|
return self.f.__get__(instance, owner) |
|
|
return self.f |
|
|
raise AttributeError |
|
|
|
|
|
|
|
|
def doctest_depends_on(exe=None, modules=None, disable_viewers=None, |
|
|
python_version=None, ground_types=None): |
|
|
""" |
|
|
Adds metadata about the dependencies which need to be met for doctesting |
|
|
the docstrings of the decorated objects. |
|
|
|
|
|
``exe`` should be a list of executables |
|
|
|
|
|
``modules`` should be a list of modules |
|
|
|
|
|
``disable_viewers`` should be a list of viewers for :func:`~sympy.printing.preview.preview` to disable |
|
|
|
|
|
``python_version`` should be the minimum Python version required, as a tuple |
|
|
(like ``(3, 0)``) |
|
|
""" |
|
|
dependencies = {} |
|
|
if exe is not None: |
|
|
dependencies['executables'] = exe |
|
|
if modules is not None: |
|
|
dependencies['modules'] = modules |
|
|
if disable_viewers is not None: |
|
|
dependencies['disable_viewers'] = disable_viewers |
|
|
if python_version is not None: |
|
|
dependencies['python_version'] = python_version |
|
|
if ground_types is not None: |
|
|
dependencies['ground_types'] = ground_types |
|
|
|
|
|
def skiptests(): |
|
|
from sympy.testing.runtests import DependencyError, SymPyDocTests, PyTestReporter |
|
|
r = PyTestReporter() |
|
|
t = SymPyDocTests(r, None) |
|
|
try: |
|
|
t._check_dependencies(**dependencies) |
|
|
except DependencyError: |
|
|
return True |
|
|
else: |
|
|
return False |
|
|
|
|
|
def depends_on_deco(fn): |
|
|
fn._doctest_depends_on = dependencies |
|
|
fn.__doctest_skip__ = skiptests |
|
|
|
|
|
if inspect.isclass(fn): |
|
|
fn._doctest_depdends_on = no_attrs_in_subclass( |
|
|
fn, fn._doctest_depends_on) |
|
|
fn.__doctest_skip__ = no_attrs_in_subclass( |
|
|
fn, fn.__doctest_skip__) |
|
|
return fn |
|
|
|
|
|
return depends_on_deco |
|
|
|
|
|
|
|
|
def public(obj: T) -> T: |
|
|
""" |
|
|
Append ``obj``'s name to global ``__all__`` variable (call site). |
|
|
|
|
|
By using this decorator on functions or classes you achieve the same goal |
|
|
as by filling ``__all__`` variables manually, you just do not have to repeat |
|
|
yourself (object's name). You also know if object is public at definition |
|
|
site, not at some random location (where ``__all__`` was set). |
|
|
|
|
|
Note that in multiple decorator setup (in almost all cases) ``@public`` |
|
|
decorator must be applied before any other decorators, because it relies |
|
|
on the pointer to object's global namespace. If you apply other decorators |
|
|
first, ``@public`` may end up modifying the wrong namespace. |
|
|
|
|
|
Examples |
|
|
======== |
|
|
|
|
|
>>> from sympy.utilities.decorator import public |
|
|
|
|
|
>>> __all__ # noqa: F821 |
|
|
Traceback (most recent call last): |
|
|
... |
|
|
NameError: name '__all__' is not defined |
|
|
|
|
|
>>> @public |
|
|
... def some_function(): |
|
|
... pass |
|
|
|
|
|
>>> __all__ # noqa: F821 |
|
|
['some_function'] |
|
|
|
|
|
""" |
|
|
if isinstance(obj, types.FunctionType): |
|
|
ns = obj.__globals__ |
|
|
name = obj.__name__ |
|
|
elif isinstance(obj, (type(type), type)): |
|
|
ns = sys.modules[obj.__module__].__dict__ |
|
|
name = obj.__name__ |
|
|
else: |
|
|
raise TypeError("expected a function or a class, got %s" % obj) |
|
|
|
|
|
if "__all__" not in ns: |
|
|
ns["__all__"] = [name] |
|
|
else: |
|
|
ns["__all__"].append(name) |
|
|
|
|
|
return obj |
|
|
|
|
|
|
|
|
def memoize_property(propfunc): |
|
|
"""Property decorator that caches the value of potentially expensive |
|
|
``propfunc`` after the first evaluation. The cached value is stored in |
|
|
the corresponding property name with an attached underscore.""" |
|
|
attrname = '_' + propfunc.__name__ |
|
|
sentinel = object() |
|
|
|
|
|
@wraps(propfunc) |
|
|
def accessor(self): |
|
|
val = getattr(self, attrname, sentinel) |
|
|
if val is sentinel: |
|
|
val = propfunc(self) |
|
|
setattr(self, attrname, val) |
|
|
return val |
|
|
|
|
|
return property(accessor) |
|
|
|
|
|
|
|
|
def deprecated(message, *, deprecated_since_version, |
|
|
active_deprecations_target, stacklevel=3): |
|
|
''' |
|
|
Mark a function as deprecated. |
|
|
|
|
|
This decorator should be used if an entire function or class is |
|
|
deprecated. If only a certain functionality is deprecated, you should use |
|
|
:func:`~.warns_deprecated_sympy` directly. This decorator is just a |
|
|
convenience. There is no functional difference between using this |
|
|
decorator and calling ``warns_deprecated_sympy()`` at the top of the |
|
|
function. |
|
|
|
|
|
The decorator takes the same arguments as |
|
|
:func:`~.warns_deprecated_sympy`. See its |
|
|
documentation for details on what the keywords to this decorator do. |
|
|
|
|
|
See the :ref:`deprecation-policy` document for details on when and how |
|
|
things should be deprecated in SymPy. |
|
|
|
|
|
Examples |
|
|
======== |
|
|
|
|
|
>>> from sympy.utilities.decorator import deprecated |
|
|
>>> from sympy import simplify |
|
|
>>> @deprecated("""\ |
|
|
... The simplify_this(expr) function is deprecated. Use simplify(expr) |
|
|
... instead.""", deprecated_since_version="1.1", |
|
|
... active_deprecations_target='simplify-this-deprecation') |
|
|
... def simplify_this(expr): |
|
|
... """ |
|
|
... Simplify ``expr``. |
|
|
... |
|
|
... .. deprecated:: 1.1 |
|
|
... |
|
|
... The ``simplify_this`` function is deprecated. Use :func:`simplify` |
|
|
... instead. See its documentation for more information. See |
|
|
... :ref:`simplify-this-deprecation` for details. |
|
|
... |
|
|
... """ |
|
|
... return simplify(expr) |
|
|
>>> from sympy.abc import x |
|
|
>>> simplify_this(x*(x + 1) - x**2) # doctest: +SKIP |
|
|
<stdin>:1: SymPyDeprecationWarning: |
|
|
<BLANKLINE> |
|
|
The simplify_this(expr) function is deprecated. Use simplify(expr) |
|
|
instead. |
|
|
<BLANKLINE> |
|
|
See https://docs.sympy.org/latest/explanation/active-deprecations.html#simplify-this-deprecation |
|
|
for details. |
|
|
<BLANKLINE> |
|
|
This has been deprecated since SymPy version 1.1. It |
|
|
will be removed in a future version of SymPy. |
|
|
<BLANKLINE> |
|
|
simplify_this(x) |
|
|
x |
|
|
|
|
|
See Also |
|
|
======== |
|
|
sympy.utilities.exceptions.SymPyDeprecationWarning |
|
|
sympy.utilities.exceptions.sympy_deprecation_warning |
|
|
sympy.utilities.exceptions.ignore_warnings |
|
|
sympy.testing.pytest.warns_deprecated_sympy |
|
|
|
|
|
''' |
|
|
decorator_kwargs = {"deprecated_since_version": deprecated_since_version, |
|
|
"active_deprecations_target": active_deprecations_target} |
|
|
def deprecated_decorator(wrapped): |
|
|
if hasattr(wrapped, '__mro__'): |
|
|
class wrapper(wrapped): |
|
|
__doc__ = wrapped.__doc__ |
|
|
__module__ = wrapped.__module__ |
|
|
_sympy_deprecated_func = wrapped |
|
|
if '__new__' in wrapped.__dict__: |
|
|
def __new__(cls, *args, **kwargs): |
|
|
sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel) |
|
|
return super().__new__(cls, *args, **kwargs) |
|
|
else: |
|
|
def __init__(self, *args, **kwargs): |
|
|
sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel) |
|
|
super().__init__(*args, **kwargs) |
|
|
wrapper.__name__ = wrapped.__name__ |
|
|
else: |
|
|
@wraps(wrapped) |
|
|
def wrapper(*args, **kwargs): |
|
|
sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel) |
|
|
return wrapped(*args, **kwargs) |
|
|
wrapper._sympy_deprecated_func = wrapped |
|
|
return wrapper |
|
|
return deprecated_decorator |
|
|
|