Python:使用 setattr 添加描述符不会调用__set_name__



这个问题可能在某个地方有答案,但我找不到。

我正在使用setattr向类添加一长串描述符,我发现在使用setattr时不会调用描述符的__set_name__

这对我来说不是什么大问题,因为我可以通过__init__设置描述符的名称,我只想了解为什么不调用__set_name__,以及是否是因为我做错了什么。

class MyDescriptor:
def __set_name__(self, owner, name):
self._owner = owner
self._name = name
def __get__(self, instance, owner):
return self._name

class MyClass:
a = MyDescriptor()

setattr(MyClass, 'b', MyDescriptor())  # dynamically adding descriptor, __set_name__ is not called!
mc = MyClass()
print(mc.a)  # prints 'a'
print(mc.b)  # raises AttributeError: 'MyDescriptor' object has no attribute '_name'

__set_name__是在type.__new__函数中创建类时为每个描述符调用的:它不是一个"反应性协议";,也就是说,每当创建描述符时都会被触发。

简单的解决方法是在调用setattr:之后手动调用__set_name__

descr_name = 'b'
setattr(MyClass, descr_name, MyDescriptor()) 
getattr(MyClass, 'b').__set_name__(MyClass, descr_name)

尽管可以有一个带有自定义__setattr__方法的元类来为您完成这项工作。类中的__setattr__方法实现是Python将反应性行为添加到属性设置中的方式——只是这一次你需要在类中而不是在实例中的行为。

如果你在很多地方都这样做,并且真的希望它是透明的,那么这可能是一件值得做的事情:

class Meta(type):
def __setattr__(cls, attr, value):
super().__setattr__(attr, value)
if (setname:=getattr(value, "__set_name__", None) ) and callable(setname):
setname(cls, attr)
class MyClass(metaclass=Meta):
a = MyDescriptor()

现在,在交互式提示中查看:


In [83]: setattr(MyClass, "b", MyDescriptor())
In [84]: MyClass.b
Out[84]: 'b'
In [85]: MyClass.__dict__["b"]._name
Out[85]: 'b'

相关内容

最新更新