SymPy 实用装饰器模块全解析:threaded、deprecated 与 memoize_property 的源码级指南
2026/9/15 12:03:58 网站建设 项目流程

SymPy 实用装饰器模块全解析:threaded、deprecated 与 memoize_property 的源码级指南

【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy

导读

sympy.utilities.decorator是 SymPy(一个用纯 Python 编写的计算机代数系统)中承载通用实用装饰器的核心工具模块,为整个代码库提供了表达式逐元素映射(threading)、API 标记、废弃管理、属性缓存与文档测试依赖声明等基础设施。本文基于仓库中 sympy/utilities/decorator.py 的完整源码与其配套测试 sympy/utilities/tests/test_decorator.py,逐一定义每个装饰器的设计意图、调用约定与实现原理,并结合partfrac.pyassumptions/ask.pyfp_groups.py等真实调用场景,帮助你在编写 SymPy 扩展或自己的符号计算库时,正确选择并灵活运用这些装饰器。

模块概览与文档来源

SymPy 官方文档中关于该模块的说明位于 doc/src/modules/utilities/decorator.rst,它通过 Sphinx 的automodule指令直接挂载sympy.utilities.decorator的成员文档,并排除deprecated成员(因其单独以autodecorator形式展示)。这意味着该模块的"文档正文"实质就是源码中的 docstring 与 doctest,本文即以此为主体展开。

模块顶层导出了以下 API:

名称类型作用
threaded装饰器把函数应用到复合对象的各个元素(含Add
xthreaded装饰器把函数应用到复合对象的各个元素(排除Add
threaded_factory工厂函数threaded/xthreaded的底层实现
no_attrs_in_subclass描述符类阻止子类"继承"基类的某些属性
doctest_depends_on装饰器为 doctest 声明运行依赖(可执行文件、模块等)
public装饰器在定义处把对象名追加进所在模块的__all__
memoize_property装饰器属性首次求值后缓存结果
deprecated装饰器标记整函数或整类为废弃

此外,模块为保持向后兼容还转导出sympy.external.mpmath.conserve_mpmath_dps(见源码 decorator.py),并依赖 sympy/utilities/exceptions.py 中的sympy_deprecation_warning

threaded 与 xthreaded:对复合表达式做逐元素映射

设计目标与签名约定

threaded的目的是"统一地让一个函数能够作用于复合对象的所有元素",这些复合对象包括矩阵、列表、元组、集合以及其他可迭代容器,或普通表达式本身。其用法约定如下:

@threaded def function(expr, *args, **kwargs): ...

被装饰的函数签名必须是function(expr, *args, **kwargs),其中第一个位置参数expr是待展开的目标对象。两个变体的差异仅在于是否对Add类表达式展开:

  • threaded:允许对Add的元素逐项应用(源码 docstring 明确说明"allows threading over elements of Add class");
  • xthreaded:禁止对Add展开,此时整个Add表达式作为整体传给函数("disallows threading over elements of Add class")。

底层实现:threaded_factory

两者都由工厂函数threaded_factory(func, use_add)生成包装器(decorator.py),threaded传入use_add=Truexthreaded传入use_add=False。包装器的分派逻辑依次为:

  1. 矩阵:若exprMatrixBase实例,则调用expr.applyfunc(lambda f: func(f, *args, **kwargs)),即对每个矩阵元素应用函数;
  2. 一般可迭代容器:若expr可迭代(通过sympy.utilities.iterables.iterable判断),尝试expr.__class__([func(f, ...) for f in expr])重建同类型容器(列表、元组、集合均可保持类型);若抛TypeError(例如某些不可重建的容器),则原样返回expr
  3. 符号表达式:先sympify(expr)规范化;若use_add为真且expr.is_Add,对每一项func(f, ...)后以expr.__class__重组;若expr.is_Relational(如Eq),对左右两侧分别应用函数后重建关系式;否则直接func(expr, ...)

由于包装器使用functools.wraps,被装饰函数的__name____doc__与自定义属性都会保留(测试test_wraps验证了这一点,见 test_decorator.py)。

行为验证

仓库测试 test_decorator.py 给出了精确的预期行为:

@threaded def function(expr, *args): return 2*expr + sum(args) function(Matrix([[x, y], [1, x]]), 1, 2) # -> Matrix([[2*x + 3, 2*y + 3], [5, 2*x + 3]]) function(Eq(x, y), 1, 2) # -> Eq(2*x + 3, 2*y + 3) function([x, y], 1, 2) # -> [2*x + 3, 2*y + 3] function((x, y), 1, 2) # -> (2*x + 3, 2*y + 3) function({x, y}, 1, 2) # -> {2*x + 3, 2*y + 3} @threaded def function(expr, n): return expr**n function(x + y, 2) # -> x**2 + y**2 (Add 被逐项展开) function(x, 2) # -> x**2 @xthreaded def function(expr, n): return expr**n function(x + y, 2) # -> (x + y)**2 (Add 保持整体)

仓库内的真实用法

一个典型的真实调用位于 sympy/polys/partfrac.py:@xthreaded被用于apart的部分分式分解函数,使得用户传入矩阵或集合时函数能自动逐元素作用,而传入多项式加法表达式时则整体处理。这体现了该装饰器在 SymPy 公共 API 中的核心价值:让数学函数天然具备对复合对象的批量能力,而无需为每种容器单独写分发代码

public:在定义处自动登记all

public装饰器的职责是把被装饰函数或类的名字追加到其所在模块的全局__all__列表中(decorator.py),从而免去手动维护__all__的重复劳动,也让"对象是公开 API"这一信息在定义处即可见。

其实现要点:

  • 对函数:通过obj.__globals__获取模块命名空间;
  • 对类:通过sys.modules[obj.__module__].__dict__获取;
  • 对其他对象:抛出TypeError
  • 若模块尚无__all__则创建,否则追加。

源码 docstring 中的 doctest 直观展示了效果:

>>> from sympy.utilities.decorator import public >>> __all__ # NameError: name '__all__' is not defined >>> @public ... def some_function(): ... pass >>> __all__ ['some_function']

重要注意事项:在多重装饰器叠加时,@public必须放在最外层(最先应用),因为它依赖指向对象全局命名空间的指针;若先应用其他装饰器,@public可能修改错误的命名空间。这一约束是官方文档明确强调的。

仓库中的典型用法如 sympy/combinatorics/fp_groups.py、sympy/combinatorics/free_groups.py、sympy/concrete/guess.py 以及 sympy/polys/appellseqs.py,都以@public标记对外暴露的函数。

memoize_property:带缓存的属性装饰器

memoize_property是一个属性装饰器,首次求值后把结果缓存在同名带下划线前缀的属性上(decorator.py):

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)

实现通过一个哨兵对象sentinel区分"从未计算"与"缓存值为空"两种情况,确保任何返回值(包括NoneFalse等假值)都能被正确缓存;缓存只挂在实例上,不污染类。测试 test_decorator.py 验证了两次访问返回同一个对象(obj1 is obj2)。

仓库中大规模使用该装饰器的典型位置是 sympy/assumptions/ask.py,其中AskHandler相关类用大量@memoize_property缓存一次计算得到的谓词处理器表——这些属性求值成本较高,而结果在对象生命周期内不变,非常适合缓存。

doctest_depends_on:声明 doctest 的运行依赖

doctest_depends_on为被装饰对象(函数或类)的 docstring 测试声明所需满足的依赖,缺失依赖时自动跳过对应 doctest(decorator.py)。可选参数:

参数类型含义
exe可执行文件列表doctest 运行所需的外部可执行程序
modules模块列表doctest 运行所需导入的 Python 模块
disable_viewers查看器列表需要为sympy.printing.preview.preview禁用的查看器
python_version元组所需的最低 Python 版本,如(3, 0)
ground_types列表所需的 ground types(与多项式域相关)

实现上,它把依赖信息存入fn._doctest_depends_on,并把跳过判定函数挂到fn.__doctest_skip__;判定逻辑惰性导入sympy.testing.runtests中的SymPyDocTests._check_dependencies,依赖不满足时抛DependencyError从而返回"跳过"。特别地,当装饰对象是类时,依赖元数据用no_attrs_in_subclass包裹,防止被子类"继承"(见下节)。

真实用例:sympy/categories/diagram_drawing.py 中@doctest_depends_on(exe=('latex', 'dvipng'), modules=('pyglet',)),表明该对象的 doctest 需要 LaTeX、dvipng 可执行文件以及 pyglet 模块,缺失时测试框架自动跳过。

no_attrs_in_subclass:阻止属性被子类继承

no_attrs_in_subclass是一个描述符类,用于阻止子类继承基类的某些属性(decorator.py)。用法:

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

__get__逻辑是:只有当owner == self.cls(即访问者正是当初绑定该描述符的类本身)时才返回原属性值,否则抛AttributeError。这样doctest_depends_on装饰类时,依赖元数据不会"泄漏"给子类——每个类都必须显式声明自己的 doctest 依赖,这正是 decorator.py 中用它包裹元数据的原因。

deprecated:标记整函数或整类为废弃

用法与参数

deprecated装饰器用于标记整个函数或类为废弃;若只是部分功能废弃,则应直接在函数体内调用sympy_deprecation_warning()。官方明确说明"该装饰器与在函数开头调用warns_deprecated_sympy()在功能上没有区别,只是便利性封装"。

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

参数与sympy_deprecation_warning一致:

  • message:废弃说明,最终会拼入警告全文;
  • deprecated_since_version:自哪个 SymPy 版本起废弃(字符串);
  • active_deprecations_target:指向 doc/src/explanation/active-deprecations.md 中对应条目的锚点名;
  • stacklevel(默认 3):控制警告堆栈层级,指向用户代码调用处。

实现原理:函数与类的不同包装路径

源码 decorator.py 根据被装饰对象是否为类(通过hasattr(wrapped, '__mro__')判断)走两条路径:

  • 函数:用functools.wraps生成包装器,每次调用先触发sympy_deprecation_warning(message, **decorator_kwargs, stacklevel=stacklevel),再调用原函数;并把原函数挂到wrapper._sympy_deprecated_func便于内省;
  • :动态创建wrapper(wrapped)子类,保留__doc____module____name__;若原类自定义了__new__则在__new__中触发警告,否则在__init__中触发。这一设计保证了无论实例化路径如何(含__new__返回非本类对象的场景)警告都会发出——测试test_deprecated__new__返回arg__new__/__init__混用的类验证了这一点(test_decorator.py)。

配套的警告基础设施

deprecated依赖 sympy/utilities/exceptions.py 中的SymPyDeprecationWarningDeprecationWarning的子类)。它有两个关键设计:

  1. 通过warnings.simplefilter("once", SymPyDeprecationWarning)(exceptions.py)让废弃警告默认可见(普通DeprecationWarning在非交互式场景默认被隐藏);
  2. 警告全文会自动附上"自 SymPy 版本 X 起废弃,将在未来版本移除"与指向 active-deprecations 文档的链接(exceptions.py)。

测试中通过sympy.testing.pytest.warns_deprecated_sympy上下文断言警告确实触发(test_decorator.py)。

仓库内的真实用例

@deprecated在仓库中广泛用于逐步淘汰旧 API,例如 sympy/ntheory/factor_.py 与 sympy/ntheory/generate.py 中对旧版数论函数的标记。若要了解 SymPy 的废弃策略(何时可弃用、如何走完弃用周期、如何移除),可阅读 doc/src/contributing/deprecations.md。

使用建议与注意事项汇总

  1. 优先xthreaded还是threaded若你的函数对Add表达式整体才有数学意义(如(x + y)**2不应展开为x**2 + y**2),必须用xthreaded;只有希望函数严格逐项作用于和式时(如apart之外的逐项变换)才用threaded。参见 partfrac.py 的取舍。
  2. @public的摆放位置:与其他装饰器组合时务必置于最外层,否则可能污染错误模块的__all__
  3. memoize_property的适用前提:只对"首次求值成本高且结果不可变"的属性使用;缓存值存放在实例的_属性名上,注意与手动定义的_前缀属性避免命名冲突。
  4. 废弃策略:只废弃整个函数/类时用@deprecated,部分废弃时直接在函数体内调用sympy_deprecation_warning()active_deprecations_target必须对应 doc/src/explanation/active-deprecations.md 中真实存在的锚点。
  5. doctest 依赖声明:当 docstring 示例依赖外部程序或第三方库时,务必用@doctest_depends_on声明,否则 CI 与本地测试会因环境差异误报失败。

深入阅读

  • 模块完整实现:sympy/utilities/decorator.py
  • 单元测试与行为契约:sympy/utilities/tests/test_decorator.py
  • 废弃警告基础设施:sympy/utilities/exceptions.py
  • 废弃策略文档:doc/src/contributing/deprecations.md
  • 现行废弃条目清单:doc/src/explanation/active-deprecations.md
  • 官方 API 文档入口:doc/src/modules/utilities/decorator.rst

【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询