__set_name__ 在描述符中执行

__set_name__ execution in Descriptor

我发现了一个包含描述符的代码。据我了解, __set_name__ 是在创建 class 时调用的方法。然后,如果 class 被调用两次,我会接到两个调用。

在下面的代码片段中,我希望在 __set_name__ 中接到两次电话,但我只接到了一次电话。为什么会出现这种行为?

class SharedAttribute:
    def __init__(self, initial_value=None):
        self.value = initial_value
        self._name = None
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        if self.value is None:
            raise AttributeError(f'{self._name} was never set')
        return self.value

    def __set__(self, instance, new_value):
        self.value = new_value

    def __set_name__(self, owner, name):
        print(f'{self} was named {name} by {owner}')
        self._name = name


class GitFetcher:
    current_tag = SharedAttribute()
    current_branch = SharedAttribute()

    def __init__(self, tag, branch=None):
        self.current_tag = tag
        self.current_branch = branch
    
    @property
    def current_tag(self):
        if self._current_tag is None:
            raise AttributeError("tag was never set")
        return self._current_tag

    @current_tag.setter
    def current_tag(self, new_tag):
        self.__class__._current_tag = new_tag

    def pull(self):
        print(f"pulling from {self.current_tag}")
        return self.current_tag


f1 = GitFetcher(0.1)
f2 = GitFetcher(0.2)
f1.current_tag = 0.3
f2.pull()
f1.pull()

在之前的执行过程中,__set_name__被current_branch调用,但没有被current_tag调用。为什么会有这种区别?唯一的调用是这个:

<__main__.SharedAttribute object at 0x047BACB0> was named current_branch by <class '__main__.GitFetcher'>

TL;DR 在调用 __set_name__ 方法时,current_tag 指的是 property 的实例,而不是 SharedAttribute.[=30= 的实例]


__set_name__ 在 之后被调用 class 已经被定义(这样 class 可以作为 owner 参数),而不是在赋值后立即进行。

但是,您将 current_tag 的值更改为 property,因此一旦 class 定义具有,该名称就不再绑定到 SharedAttribute 实例完成。'

来自 documentation(强调我的):

Automatically called at the time the owning class owner is created.

SharedAttribute 实例是在执行 class 语句的主体时创建的。 class 本身直到 主体执行后才会创建;执行正文的结果是一个命名空间,它作为参数传递给创建 class 的 metaclass。在此过程中,使用 __set_name__ 方法扫描 class 属性的值,只有 then 是调用的方法。

这是一个更简单的例子:

class GitFetcher:
    current_branch = SharedAttribute()
    current_tag = SharedAttribute()
    current_tag = 3

在定义 GitFetcher 时,current_tag 不再绑定到描述符,因此不会尝试调用 current_tag.__set_name__

尚不清楚您是否想以某种方式将 属性 与 SharedAttribute 组合在一起,或者这是否只是名称 current_tag 的无意 re-use。