如何在函数声明中向 python 函数对象添加自定义属性?

How can you add custom attributes to a python function object within the function declaration?

提问人:MYK 提问时间:12/7/2021 最后编辑:MYK 更新时间:12/8/2021 访问量:689

问:

我希望创建一个注释丰富的代码库。我记得 Python 中的函数是对象,因此它们允许任意属性。例如:

def foo(x:int, y:int)->int:
    '''This is a doc string'''
    return x**2 + y**2

foo.__notes__ = {
    'Dependencies':['x', 'y']
}

foo.__notes__

>>> {'Dependencies': ['x', 'y']}

我希望从函数定义中设置属性。关于如何实现这一目标的任何想法?__notes__


编辑:正如评论中指出的那样,这个属性是一个糟糕的名称。 更合适。__notes___notes

评论

3赞 chepner 12/7/2021
在实际调用函数之前,定义中的任何内容都不会被计算(然后在每次调用函数时都会计算)。
1赞 Paul Cornelius 12/7/2021
丰富的注释,但适合哪些受众?不适用于其他程序员,因为为此我们有注释 #。不适用于自动文档生成器,因为为此,我们有文档字符串和标准的 pydoc 模块。您认为可用工具的哪些属性不足?
1赞 chepner 12/7/2021
@PaulCornelius 公平地说,doc 字符串只是在函数上设置字符串值属性的一种方式。您可能还需要类型更丰富的属性。__doc__
0赞 MYK 12/7/2021
@PaulCornelius - 我承认其中一部分是纯粹的好奇心,看看它是否可以做到。我打算用它来支持工具提示、别名、映射数据依赖关系、对缺失值的假设等。我可以为它使用“更大”的数据结构(例如自定义类),但认为尝试新的东西可能会很有趣。我现在在字符串中有一些东西,我通过正则表达式提取它们 - 这是为了取代它。此外,我可以嵌入非字符串对象,例如小熊猫 DataFrames。__doc__

答:

6赞 chepner 12/7/2021 #1

定义函数后,可以使用装饰器附加注释。

def add_note(k, v):
    def _(f):
        if not hasattr(f, '_notes'):
            f._notes = {}
        f._notes[k] = v
        return f
    return _


@add_note('Dependencies', ['x', 'y'])
def foo(x: int, y: int) -> int:
    return x**2 + y**2

顺便说一句,dunder 名称 (like ) 是为实现保留的;你不应该发明你自己的。只需使用一个普通的“私人”名称,前缀为一个 .__notes___

评论

0赞 MYK 12/7/2021
这看起来很干净,谢谢。你能想出一种方法将函数中声明的对象添加到属性中吗?_notes
0赞 chepner 12/7/2021
我不建议使用任何东西。我假设您希望在第一次执行函数之前添加它们,即使您可以接受每次调用函数时重新添加它们。做到这一点的唯一方法是反省源代码本身,我认为这确实比它的价值更麻烦。
0赞 MYK 12/7/2021
是的,总的来说,我同意。我会继续你的装饰师。
0赞 MYK 12/7/2021
我觉得你对下划线的使用有点奇怪。你能解释一下吗?即。你为什么要做或?def _(f):return _
0赞 JL Peyret 12/8/2021 #2

我会以不同的方式回答。函数可以在其正文中引用自己的名称,并且可以为其找到的任何内容分配/读取值。

在你的例子中,假设你想自省一个函数的作用,你要做的是从其对象上修饰的内容来驱动函数行为。

请注意,在我下面的示例中 - 它执行您想要的操作并从函数体中进行更新,直到它至少执行一次后,才会从内省工具的 POV 中记录下来。adder3

adder2另一方面,它被设置为根据其注释调整其行为,并且注释始终对内省工具可见。

adder4添加以显示此方法的一个限制。引用函数的名称并不能保证您实际上正在访问该函数,只能保证函数名称在全局变量中解析为的任何内容。


def adder1(x:int = 1, y: int = 2, z: int =3 )->int:
    '''This is a doc string'''
    return x**2 + y**2

def decorate(**kwds):
    """ assign kwds as attributes to the function object """
    def actual_decorator(func):
        for k, v in kwds.items():
            setattr(func, k, v)
        return func
    return actual_decorator

depends = {"x","y"}
@decorate(_note=f"depends:{depends}", depends=depends)
def adder2(x:int = 1, y: int = 2, z: int =3 )->int:
    # a function can totally refer to its name 
    # and use annotations to dynamically adjust its behavior
    depends = adder2.depends
    #sum up dependencies
    li = []
    for key in depends:
        v = locals()[key] ** 2
        li.append(v)

    return sum(li)        

def adder3(x:int = 1, y: int = 2, z: int =3 )->int:
    """ no note until function execution so this won't allow tools to inspect the module"""
    _note = getattr(adder3,"_note",None)
    if not _note:
        adder3._note = "depends x,y"
    return x**2 + y**2

def adder4(x:int = 1, y: int = 2, z: int =3 )->int:
    """there is no guarantee that referring by its own name works"""
    print("\n\nadder4.name:", adder4.__name__)
    return (x**2 + y**2) * 1000

adder4_saved = adder4
#swap name
adder4 = adder2 

for func in [adder1,adder2,adder3,adder4_saved]:
    print(f"\n{func.__name__} [note(before)={getattr(func,'_note',None)}] -> {func()} [note(after)={getattr(func,'_note',None)}]")


输出:


adder1 [note(before)=None] -> 5 [note(after)=None]

adder2 [note(before)=depends:{'y', 'x'}] -> 5 [note(after)=depends:{'y', 'x'}]
adder3 [note(before)=None] -> 5 [note(after)=depends x,y]


adder4.name: adder2

adder4 [note(before)=None] -> 5000 [note(after)=None]