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

6 python setattr python-descriptors

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

我用来setattr向类添加一长串描述符,并且我发现__set_name__当我使用setattr.

这对我来说不是一个大问题,因为我可以通过设置描述符的名称来代替__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'
Run Code Online (Sandbox Code Playgroud)

jsb*_*eno 4

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

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

descr_name = 'b'

setattr(MyClass, descr_name, MyDescriptor()) 
getattr(MyClass, 'b').__set_name__(MyClass, descr_name)
Run Code Online (Sandbox Code Playgroud)

尽管可以有一个带有自定义__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()

Run Code Online (Sandbox Code Playgroud)

现在,在交互式提示上检查它:


In [83]: setattr(MyClass, "b", MyDescriptor())

In [84]: MyClass.b
Out[84]: 'b'

In [85]: MyClass.__dict__["b"]._name
Out[85]: 'b'
Run Code Online (Sandbox Code Playgroud)