向 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 ...
以计算数学函数的 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 ...