这个问题可能在某个地方有答案,但我找不到。
我正在使用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'