【问题标题】:Best way to add attributes to a Python function向 Python 函数添加属性的最佳方法
【发布时间】:2017-11-01 13:30:47
【问题描述】:

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

假设我想以函数属性的形式“附加”一些进一步的信息。例如,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 在执行函数时“懒惰”(?))

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

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$')

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

【问题讨论】:

    标签: python python-3.x function introspection


    【解决方案1】:

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

    基于函数的装饰器:

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

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

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

    基于类的装饰器:

    如果类是您的偏好,那么基于类的装饰器也可以以比您最初的尝试更 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$') 初始化类并返回可调用实例(__call__ 已定义)。这是做什么的:

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

    和wrapped 做同样的事情。


    由于他们都只是向函数添加了一个参数,所以他们只是按原样返回它。他们不会将其替换为另一个可调用对象。

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


    你又问了:
    “因为 Python 在执行函数时很“懒惰””:Python 只是编译函数体,它不执行其中的任何语句;它执行的唯一事情是默认参数值(参见著名的 Q here)。这就是为什么您首先需要为其调用函数以“获取”属性latex。

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

    【讨论】:

    • 我认为您的编辑有一些剩余。目前这一位“这就是为什么你首先需要调用函数来'获取'属性乳胶。”还有这一点:“这种方法的另一个缺点是每次调用函数时都执行该赋值”是不正确的——在函数定义期间应用了装饰器,并且在那个时候进行了一次赋值。
    • @MariusGedminas 这些不是指装饰器,而是指在函数内部分配属性的 OP 方法。如果您看到他的问题,他还会问“因为 Python 在执行函数时很“懒惰”(?)”。不过,我知道它是多么令人困惑,我会将其添加到答案的末尾。感谢您的评论:-)
    【解决方案2】:

    由于您将函数视为比普通 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 ...
    

    【讨论】:

      猜你喜欢
      • 2015-03-16
      • 1970-01-01
      • 1970-01-01
      • 2017-08-26
      • 2014-07-16
      • 1970-01-01
      • 2018-01-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多