图片解析应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

331 lines
11 KiB

  1. """Useful utility decorators. """
  2. import sys
  3. import types
  4. import inspect
  5. from functools import wraps, update_wrapper
  6. from sympy.testing.runtests import DependencyError, SymPyDocTests, PyTestReporter
  7. from sympy.utilities.exceptions import sympy_deprecation_warning
  8. def threaded_factory(func, use_add):
  9. """A factory for ``threaded`` decorators. """
  10. from sympy.core import sympify
  11. from sympy.matrices import MatrixBase
  12. from sympy.utilities.iterables import iterable
  13. @wraps(func)
  14. def threaded_func(expr, *args, **kwargs):
  15. if isinstance(expr, MatrixBase):
  16. return expr.applyfunc(lambda f: func(f, *args, **kwargs))
  17. elif iterable(expr):
  18. try:
  19. return expr.__class__([func(f, *args, **kwargs) for f in expr])
  20. except TypeError:
  21. return expr
  22. else:
  23. expr = sympify(expr)
  24. if use_add and expr.is_Add:
  25. return expr.__class__(*[ func(f, *args, **kwargs) for f in expr.args ])
  26. elif expr.is_Relational:
  27. return expr.__class__(func(expr.lhs, *args, **kwargs),
  28. func(expr.rhs, *args, **kwargs))
  29. else:
  30. return func(expr, *args, **kwargs)
  31. return threaded_func
  32. def threaded(func):
  33. """Apply ``func`` to sub--elements of an object, including :class:`~.Add`.
  34. This decorator is intended to make it uniformly possible to apply a
  35. function to all elements of composite objects, e.g. matrices, lists, tuples
  36. and other iterable containers, or just expressions.
  37. This version of :func:`threaded` decorator allows threading over
  38. elements of :class:`~.Add` class. If this behavior is not desirable
  39. use :func:`xthreaded` decorator.
  40. Functions using this decorator must have the following signature::
  41. @threaded
  42. def function(expr, *args, **kwargs):
  43. """
  44. return threaded_factory(func, True)
  45. def xthreaded(func):
  46. """Apply ``func`` to sub--elements of an object, excluding :class:`~.Add`.
  47. This decorator is intended to make it uniformly possible to apply a
  48. function to all elements of composite objects, e.g. matrices, lists, tuples
  49. and other iterable containers, or just expressions.
  50. This version of :func:`threaded` decorator disallows threading over
  51. elements of :class:`~.Add` class. If this behavior is not desirable
  52. use :func:`threaded` decorator.
  53. Functions using this decorator must have the following signature::
  54. @xthreaded
  55. def function(expr, *args, **kwargs):
  56. """
  57. return threaded_factory(func, False)
  58. def conserve_mpmath_dps(func):
  59. """After the function finishes, resets the value of mpmath.mp.dps to
  60. the value it had before the function was run."""
  61. import mpmath
  62. def func_wrapper(*args, **kwargs):
  63. dps = mpmath.mp.dps
  64. try:
  65. return func(*args, **kwargs)
  66. finally:
  67. mpmath.mp.dps = dps
  68. func_wrapper = update_wrapper(func_wrapper, func)
  69. return func_wrapper
  70. class no_attrs_in_subclass:
  71. """Don't 'inherit' certain attributes from a base class
  72. >>> from sympy.utilities.decorator import no_attrs_in_subclass
  73. >>> class A(object):
  74. ... x = 'test'
  75. >>> A.x = no_attrs_in_subclass(A, A.x)
  76. >>> class B(A):
  77. ... pass
  78. >>> hasattr(A, 'x')
  79. True
  80. >>> hasattr(B, 'x')
  81. False
  82. """
  83. def __init__(self, cls, f):
  84. self.cls = cls
  85. self.f = f
  86. def __get__(self, instance, owner=None):
  87. if owner == self.cls:
  88. if hasattr(self.f, '__get__'):
  89. return self.f.__get__(instance, owner)
  90. return self.f
  91. raise AttributeError
  92. def doctest_depends_on(exe=None, modules=None, disable_viewers=None, python_version=None):
  93. """
  94. Adds metadata about the dependencies which need to be met for doctesting
  95. the docstrings of the decorated objects.
  96. exe should be a list of executables
  97. modules should be a list of modules
  98. disable_viewers should be a list of viewers for preview() to disable
  99. python_version should be the minimum Python version required, as a tuple
  100. (like (3, 0))
  101. """
  102. dependencies = {}
  103. if exe is not None:
  104. dependencies['executables'] = exe
  105. if modules is not None:
  106. dependencies['modules'] = modules
  107. if disable_viewers is not None:
  108. dependencies['disable_viewers'] = disable_viewers
  109. if python_version is not None:
  110. dependencies['python_version'] = python_version
  111. def skiptests():
  112. r = PyTestReporter()
  113. t = SymPyDocTests(r, None)
  114. try:
  115. t._check_dependencies(**dependencies)
  116. except DependencyError:
  117. return True # Skip doctests
  118. else:
  119. return False # Run doctests
  120. def depends_on_deco(fn):
  121. fn._doctest_depends_on = dependencies
  122. fn.__doctest_skip__ = skiptests
  123. if inspect.isclass(fn):
  124. fn._doctest_depdends_on = no_attrs_in_subclass(
  125. fn, fn._doctest_depends_on)
  126. fn.__doctest_skip__ = no_attrs_in_subclass(
  127. fn, fn.__doctest_skip__)
  128. return fn
  129. return depends_on_deco
  130. def public(obj):
  131. """
  132. Append ``obj``'s name to global ``__all__`` variable (call site).
  133. By using this decorator on functions or classes you achieve the same goal
  134. as by filling ``__all__`` variables manually, you just do not have to repeat
  135. yourself (object's name). You also know if object is public at definition
  136. site, not at some random location (where ``__all__`` was set).
  137. Note that in multiple decorator setup (in almost all cases) ``@public``
  138. decorator must be applied before any other decorators, because it relies
  139. on the pointer to object's global namespace. If you apply other decorators
  140. first, ``@public`` may end up modifying the wrong namespace.
  141. Examples
  142. ========
  143. >>> from sympy.utilities.decorator import public
  144. >>> __all__ # noqa: F821
  145. Traceback (most recent call last):
  146. ...
  147. NameError: name '__all__' is not defined
  148. >>> @public
  149. ... def some_function():
  150. ... pass
  151. >>> __all__ # noqa: F821
  152. ['some_function']
  153. """
  154. if isinstance(obj, types.FunctionType):
  155. ns = obj.__globals__
  156. name = obj.__name__
  157. elif isinstance(obj, (type(type), type)):
  158. ns = sys.modules[obj.__module__].__dict__
  159. name = obj.__name__
  160. else:
  161. raise TypeError("expected a function or a class, got %s" % obj)
  162. if "__all__" not in ns:
  163. ns["__all__"] = [name]
  164. else:
  165. ns["__all__"].append(name)
  166. return obj
  167. def memoize_property(propfunc):
  168. """Property decorator that caches the value of potentially expensive
  169. `propfunc` after the first evaluation. The cached value is stored in
  170. the corresponding property name with an attached underscore."""
  171. attrname = '_' + propfunc.__name__
  172. sentinel = object()
  173. @wraps(propfunc)
  174. def accessor(self):
  175. val = getattr(self, attrname, sentinel)
  176. if val is sentinel:
  177. val = propfunc(self)
  178. setattr(self, attrname, val)
  179. return val
  180. return property(accessor)
  181. def deprecated(message, *, deprecated_since_version,
  182. active_deprecations_target, stacklevel=3):
  183. '''
  184. Mark a function as deprecated.
  185. This decorator should be used if an entire function or class is
  186. deprecated. If only a certain functionality is deprecated, you should use
  187. :func:`~.warns_deprecated_sympy` directly. This decorator is just a
  188. convenience. There is no functional difference between using this
  189. decorator and calling ``warns_deprecated_sympy()`` at the top of the
  190. function.
  191. The decorator takes the same arguments as
  192. :func:`~.warns_deprecated_sympy`. See its
  193. documentation for details on what the keywords to this decorator do.
  194. See the :ref:`deprecation-policy` document for details on when and how
  195. things should be deprecated in SymPy.
  196. Examples
  197. ========
  198. >>> from sympy.utilities.decorator import deprecated
  199. >>> from sympy import simplify
  200. >>> @deprecated("""\
  201. ... The simplify_this(expr) function is deprecated. Use simplify(expr)
  202. ... instead.""", deprecated_since_version="1.1",
  203. ... active_deprecations_target='simplify-this-deprecation')
  204. ... def simplify_this(expr):
  205. ... """
  206. ... Simplify ``expr``.
  207. ...
  208. ... .. deprecated:: 1.1
  209. ...
  210. ... The ``simplify_this`` function is deprecated. Use :func:`simplify`
  211. ... instead. See its documentation for more information. See
  212. ... :ref:`simplify-this-deprecation` for details.
  213. ...
  214. ... """
  215. ... return simplify(expr)
  216. >>> from sympy.abc import x
  217. >>> simplify_this(x*(x + 1) - x**2) # doctest: +SKIP
  218. <stdin>:1: SymPyDeprecationWarning:
  219. <BLANKLINE>
  220. The simplify_this(expr) function is deprecated. Use simplify(expr)
  221. instead.
  222. <BLANKLINE>
  223. See https://docs.sympy.org/latest/explanation/active-deprecations.html#simplify-this-deprecation
  224. for details.
  225. <BLANKLINE>
  226. This has been deprecated since SymPy version 1.1. It
  227. will be removed in a future version of SymPy.
  228. <BLANKLINE>
  229. simplify_this(x)
  230. x
  231. See Also
  232. ========
  233. sympy.utilities.exceptions.SymPyDeprecationWarning
  234. sympy.utilities.exceptions.sympy_deprecation_warning
  235. sympy.utilities.exceptions.ignore_warnings
  236. sympy.testing.pytest.warns_deprecated_sympy
  237. '''
  238. decorator_kwargs = dict(deprecated_since_version=deprecated_since_version,
  239. active_deprecations_target=active_deprecations_target)
  240. def deprecated_decorator(wrapped):
  241. if hasattr(wrapped, '__mro__'): # wrapped is actually a class
  242. class wrapper(wrapped):
  243. __doc__ = wrapped.__doc__
  244. __module__ = wrapped.__module__
  245. _sympy_deprecated_func = wrapped
  246. if '__new__' in wrapped.__dict__:
  247. def __new__(cls, *args, **kwargs):
  248. sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel)
  249. return super().__new__(cls, *args, **kwargs)
  250. else:
  251. def __init__(self, *args, **kwargs):
  252. sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel)
  253. super().__init__(*args, **kwargs)
  254. wrapper.__name__ = wrapped.__name__
  255. else:
  256. @wraps(wrapped)
  257. def wrapper(*args, **kwargs):
  258. sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel)
  259. return wrapped(*args, **kwargs)
  260. wrapper._sympy_deprecated_func = wrapped
  261. return wrapper
  262. return deprecated_decorator