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

1"""Useful utility decorators. """ 

2 

3import sys 

4import types 

5import inspect 

6from functools import wraps, update_wrapper 

7 

8from sympy.utilities.exceptions import sympy_deprecation_warning 

9 

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 

15 

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) 

27 

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) 

35 

36 return threaded_func 

37 

38 

39def threaded(func): 

40 """Apply ``func`` to sub--elements of an object, including :class:`~.Add`. 

41 

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. 

45 

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. 

49 

50 Functions using this decorator must have the following signature:: 

51 

52 @threaded 

53 def function(expr, *args, **kwargs): 

54 

55 """ 

56 return threaded_factory(func, True) 

57 

58 

59def xthreaded(func): 

60 """Apply ``func`` to sub--elements of an object, excluding :class:`~.Add`. 

61 

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. 

65 

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. 

69 

70 Functions using this decorator must have the following signature:: 

71 

72 @xthreaded 

73 def function(expr, *args, **kwargs): 

74 

75 """ 

76 return threaded_factory(func, False) 

77 

78 

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 

83 

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 

90 

91 func_wrapper = update_wrapper(func_wrapper, func) 

92 return func_wrapper 

93 

94 

95class no_attrs_in_subclass: 

96 """Don't 'inherit' certain attributes from a base class 

97 

98 >>> from sympy.utilities.decorator import no_attrs_in_subclass 

99 

100 >>> class A(object): 

101 ... x = 'test' 

102 

103 >>> A.x = no_attrs_in_subclass(A, A.x) 

104 

105 >>> class B(A): 

106 ... pass 

107 

108 >>> hasattr(A, 'x') 

109 True 

110 >>> hasattr(B, 'x') 

111 False 

112 

113 """ 

114 def __init__(self, cls, f): 

115 self.cls = cls 

116 self.f = f 

117 

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 

124 

125 

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. 

130 

131 exe should be a list of executables 

132 

133 modules should be a list of modules 

134 

135 disable_viewers should be a list of viewers for preview() to disable 

136 

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 

149 

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 

160 

161 def depends_on_deco(fn): 

162 fn._doctest_depends_on = dependencies 

163 fn.__doctest_skip__ = skiptests 

164 

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 

171 

172 return depends_on_deco 

173 

174 

175def public(obj): 

176 """ 

177 Append ``obj``'s name to global ``__all__`` variable (call site). 

178 

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). 

183 

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. 

188 

189 Examples 

190 ======== 

191 

192 >>> from sympy.utilities.decorator import public 

193 

194 >>> __all__ # noqa: F821 

195 Traceback (most recent call last): 

196 ... 

197 NameError: name '__all__' is not defined 

198 

199 >>> @public 

200 ... def some_function(): 

201 ... pass 

202 

203 >>> __all__ # noqa: F821 

204 ['some_function'] 

205 

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) 

215 

216 if "__all__" not in ns: 

217 ns["__all__"] = [name] 

218 else: 

219 ns["__all__"].append(name) 

220 

221 return obj 

222 

223 

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() 

230 

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 

238 

239 return property(accessor) 

240 

241 

242def deprecated(message, *, deprecated_since_version, 

243 active_deprecations_target, stacklevel=3): 

244 ''' 

245 Mark a function as deprecated. 

246 

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. 

253 

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. 

257 

258 See the :ref:`deprecation-policy` document for details on when and how 

259 things should be deprecated in SymPy. 

260 

261 Examples 

262 ======== 

263 

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 

297 

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 

304 

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