123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- """Useful utility decorators. """
- import sys
- import types
- import inspect
- from functools import wraps, update_wrapper
- from sympy.utilities.exceptions import sympy_deprecation_warning
- 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):
- """
- 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 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
- def skiptests():
- from sympy.testing.runtests import DependencyError, SymPyDocTests, PyTestReporter # lazy import
- r = PyTestReporter()
- t = SymPyDocTests(r, None)
- try:
- t._check_dependencies(**dependencies)
- except DependencyError:
- return True # Skip doctests
- else:
- return False # Run doctests
- 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):
- """
- 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__'): # wrapped is actually a class
- 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
|