【问题标题】:Python setattr() to function takes initial function namePython setattr() 函数采用初始函数名
【发布时间】:2019-08-09 23:55:21
【问题描述】:

我确实了解setattr() 在 python 中的工作原理,但我的问题是,当我尝试动态设置属性并为其提供未绑定函数作为值时,因此该属性是可调用的,该属性最终取名当我调用 attr.__name__ 而不是属性的名称时,未绑定函数。

这是一个例子:

我有一个Filter 类:

class Filter:
    def __init__(self, column=['poi_id', 'tp.event'], access=['con', 'don']):
        self.column = column
        self.access = access
        self.accessor_column = dict(zip(self.access, self.column))
        self.set_conditions()

    def condition(self, name):
        # i want to be able to get the name of the dynamically set 
        # function and check `self.accessor_column` for a value, but when
        # i do `setattr(self, 'accessor', self.condition)`, the function 
        # name is always set to `condition` rather than `accessor`
        return name

    def set_conditions(self):
        mapping = list(zip(self.column, self.access))
        for i in mapping:
            poi_column = i[0]
            accessor = i[1]
            setattr(self, accessor, self.condition)

在上面的类中,set_conditions 函数动态设置 Filter 类的属性(condon)并为其分配一个可调用对象,但它们保留了函数的初始名称。

当我运行这个时:

>>> f = Filter()
>>> print(f.con('linux'))
>>> print(f.con.__name__)

预期:

  • Linux
  • con(应该是动态设置属性的名称)

我明白了:

  • Linux
  • 条件(属性值的名称(未绑定self.condition))

但我希望 f.con.__name__ 返回属性的名称 (con) 而不是分配给它的未绑定函数的名称 (condition)。

有人可以向我解释为什么会出现这种行为,我该如何解决?

谢谢。

【问题讨论】:

标签: python class setattribute setattr dynamic-function


【解决方案1】:

function.__name__ 是函数最初定义的名称,它与访问它的名称无关。实际上,function.__name__ 的全部意义在于正确识别用于访问它的任何名称的函数。你肯定想read this for more on what Python's "names" are

这里可能的解决方案之一是将condition 的静态定义替换为闭包:

class Filter(object):
    def __init__(self, column=['poi_id', 'tp.event'], access=['con', 'don']):
        self.column = column
        self.access = access
        self.accessor_column = dict(zip(self.access, self.column))
        self.set_conditions()

    def set_conditions(self):
        mapping = list(zip(self.column, self.access))
        for column_name, accessor_name in mapping:
            def accessor(name):
                print("in {}.accessor '{}' for column '{}'".format(self, accessor_name, column_name))
                return name

            # this is now technically useless but helps with inspection
            accessor.__name__ = accessor_name
            setattr(self, accessor_name, accessor)

作为旁注(完全不相关,但我认为您可能想知道这一点),使用可变对象作为函数参数默认值是 one of the most infamous Python gotchas 并且可能会产生完全意想不到的结果,即:

>>> f1 = Filter()
>>> f2 = Filter()
>>> f1.column
['poi_id', 'tp.event']
>>> f2.column
['poi_id', 'tp.event']
>>> f2.column.append("WTF")
>>> f1.column
['poi_id', 'tp.event', 'WTF']

编辑:

感谢您的回答,但这与我的问题无关。我的问题不是函数是如何命名或定义的,我的问题是当我使用 setattr() 并设置一个属性并给它一个函数作为它的值时,我可以访问该值并执行该值的作用,但是因为它是一个函数,为什么它不返回它的名字作为函数名

因为正如我上面已经解释的那样,函数的__name__ 属性和引用该函数的Filter 实例属性的名称是完全不相关的,并且该函数绝对知道什么都不知道关于引用它的变量或属性的名称,如我链接到的参考文章中所述。

实际上,您传递给 setattr 的对象是一个函数这一事实完全不相关,从对象的 POV 来看,它只是一个名称和一个对象,句号。实际上,您将此对象(函数或只是 whatever 对象)绑定到实例属性(无论是直接还是使用setattr(),它的工作原理都一样)而不是普通变量这一事实也是完全不相关——这些操作都不会对绑定的对象产生任何影响(除了增加它的引用计数器,但这是一个 CPython 实现细节——其他实现可能会以不同的方式实现垃圾收集)。

【讨论】:

  • 您好 Bruno,感谢您的回答,但这与我的问题无关。我的问题不是函数是如何命名或定义的,我的问题是当我使用setattr() 并设置一个属性并给它一个函数作为它的值时,我可以访问该值并执行该值的作用,但是因为它是一个函数,为什么它不返回它的名称作为函数名称,我会查看setattr。另外关于使用可变对象作为函数参数,我不打算更新/更改(附加)到这些参数,所以我在这里没有看到任何伤害。再次感谢您。
  • @EmmanuelEvbuomwan cf 我编辑的答案 - TL;DR:我确实回答了你的问题。
【解决方案2】:

我可以建议您这样做吗:

from types import SimpleNamespace

class Filter:
    def __init__(self, column=['poi_id', 'tp.event'], access=['con', 'don']):
        self.column = column
        self.access = access
        self.accessor_column = dict(zip(self.access, self.column))
        self.set_conditions()

    def set_conditions(self):
        for i in self.access:
            setattr(self, i, SimpleNamespace(name=i, func=lambda name: name))

f = Filter()
print(f.con.func('linux'))
>>> linux
print(f.con.name)
>>> con

[根据 bruno desthuilliers 的评论编辑。]

【讨论】:

  • Python 没有指针。 f.con 实际上是一个表达式,其计算结果为 getattr(f, "con"),而 f.__dict__["con"] 是对 method 在计算 self.condition 时创建的实例的引用,该方法包含对 f 的引用, FilterFilter.__dict__["condition"].