向 Python 函数添加属性的最佳方法

Best way to add attributes to a Python function

以计算数学函数的 Python 函数为例:

def func(x, a, b, c):
    """Return the value of the quadratic function, ax^2 + bx + c."""

    return a*x**2 + b*x + c

假设我想 "attach" 一些函数属性形式的进一步信息。例如,LaTeX 表示。我知道多亏了 PEP232 我可以在函数定义之外做到这一点:

def func(x, a, b, c):
    return a*x**2 + b*x + c
func.latex = r'$ax^2 + bx + c$'

但我想在函数定义本身中进行。如果我写

def func(x, a, b, c):
    func.latex = r'$ax^2 + bx + c$'
    return a*x**2 + b*x + c

这当然有效,但是 仅在 我第一次调用 func 之后(因为 Python 是 "lazy"执行函数(?))

我唯一的选择是编写可调用的 class 吗?

class MyFunction:
     def __init__(self, func, latex):
         self.func = func
         self.latex = latex

     def __call__(self, *args, **kwargs):
         return self.func(*args, **kwargs)

func = MyFunction(lambda x,a,b,c: a*x**2+b*x+c, r'$ax^2 + bx + c$')

或者我忽略了语言的一个特性来巧妙地做到这一点?

实现此目的的更好方法是使用装饰器,为此您有两个选择:

基于函数的装饰器:

您可以创建一个基于函数的装饰器,它接受乳胶表示作为参数并将其附加到它所装饰的函数:

def latex_repr(r):
    def wrapper(f):
        f.latex = r
        return f
    return wrapper

然后您可以在定义函数时使用它并提供适当的表示形式:

@latex_repr(r'$ax^2 + bx + c$')
def func(x, a, b, c):
    return a*x**2 + b*x + c

这转化为:

func = latex_repr(r'$ax^2 + bx + c$')(func)

并在定义函数后立即使 latex 属性可用:

print(func.latex)
'$ax^2 + bx + c$'

我已将表示作为必需参数,如果您不想强制始终给出表示,则可以定义一个合理的默认值。

基于

Class的装饰器:

如果 classes 是您的偏好,基于 class 的装饰器也可以以比您最初的尝试更 Pythonic 的方式用于类似的效果:

class LatexRepr:
    def __init__(self, r):
        self.latex = r

    def __call__(self, f):
        f.latex = self.latex
        return f

你的用法是一样的:

@LatexRepr(r'$ax^2 + bx + c$')
def func(x, a, b, c):
    return a*x**2 + b*x + c

print(func.latex)
'$ax^2 + bx + c$'

此处 LatexRepr(r'$ax^2 + bx + c$') 初始化 class 和 return 可调用实例(__call__ 已定义)。这是做什么的:

func = LatexRepr(r'$ax^2 + bx + c$')(func)
#                   __init__    
#                                  __call__

wrapped做同样的事情。


因为他们都只是向函数添加一个参数,所以他们只是 return 按原样。他们不会用另一个可调用对象替换它。

虽然基于 class 的方法可以解决问题,但基于函数的装饰器应该更快、更轻便。


你另外问:
"because Python is "lazy"in executing functions": Python just compile the function body,它不执行其中的任何语句;它 做的唯一一件事 执行的是默认参数值(参见著名的 Q here)。这就是为什么您首先需要为它调用函数以 'obtain' 属性 latex

这种方法的另一个缺点是每次 调用函数

时都会执行该赋值

由于您将函数视为比普通 Python 函数更复杂的实体,因此将它们表示为指定用户定义 class 的可调用实例当然很有意义,就像您一样建议。

但是,一种更简单、更常用的方法是使用装饰器:

def with_func_attrs(**attrs):
    def with_attrs(f):
        for k,v in attrs.items():
            setattr(f, k, v)
        return f
    return with_attrs

@with_func_attrs(latex = r'$ax^2 + bx + c$', foo = 'bar')
def func(...):
    return ...