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

xnx*_*xnx 20 python function introspection python-3.x

以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
Run Code Online (Sandbox Code Playgroud)

假设我想以函数属性的形式"附加"一些其他信息.例如,LaTeX表示.我知道,由于PEP232,我可以在函数定义之外执行此操作:

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

但我想在函数定义中做到这一点.如果我写

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

这当然有效,但只有在func第一次调用之后(因为Python在执行函数时是"懒惰的"(?))

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

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$')
Run Code Online (Sandbox Code Playgroud)

或者是否有一种语言的功能,我忽略了这样做的整洁?

Jim*_*ard 21

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

基于功能的装饰器:

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

def latex_repr(r):
    def wrapper(f):
        f.latex = r
        return f
    return wrapper
Run Code Online (Sandbox Code Playgroud)

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

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

这意味着:

func = latex_repr(r'$ax^2 + bx + c$')(func)
Run Code Online (Sandbox Code Playgroud)

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

print(func.latex)
'$ax^2 + bx + c$'
Run Code Online (Sandbox Code Playgroud)

我已经使表示成为必需参数,如果您不想强制始终给出表示,您可以定义合理的默认值.

基于类的装饰者:

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

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

    def __call__(self, f):
        f.latex = self.latex
        return f
Run Code Online (Sandbox Code Playgroud)

你以同样的方式使用它:

@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$'
Run Code Online (Sandbox Code Playgroud)

这里LatexRepr(r'$ax^2 + bx + c$')初始化类并返回可调用实例(已__call__定义).这样做是:

func = LatexRepr(r'$ax^2 + bx + c$')(func)
#                   __init__    
#                                  __call__
Run Code Online (Sandbox Code Playgroud)

并做同样的事情wrapped.


因为它们都只是为函数添加一个参数,所以它们只是按原样返回.它们不会用另一个可调用的替换它.

虽然基于类的方法可以解决这个问题,但基于函数的装饰器应该更快,更轻巧.


你还问:
"因为Python在执行函数时是"懒惰的":Python只是编译函数体,它不会在其中执行任何语句; 它唯一执行是默认参数值(见著名的Q 这里).这就是为什么你首先需要调用函数来"获取"属性latex.

这种方法的另一个缺点是每次调用函数时都执行该赋值


shx*_*hx2 6

由于您将函数视为比普通Python函数更复杂的实体,因此将它们表示为指定的用户定义类的可调用实例(如您所建议的那样)确实很有意义.

但是,一个更简单和常见的方法来做你想要的是使用装饰器:

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 ...
Run Code Online (Sandbox Code Playgroud)