I have came across a code that it is including descriptors. As I understand, __set_name__
is a method that is called when the class is created. Then, if the class is called twice I'd get two calls.
In the following snippet I would expect to get the call in __set_name__
twice, but I am getting just one call. Why this behavior?
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()
During the previous execution, __set_name__
is called with current_branch, but not called with current_tag. Why this distinction? The only call is this one:
<__main__.SharedAttribute object at 0x047BACB0> was named current_branch by <class '__main__.GitFetcher'>
TL;DR By the time __set_name__
methods are called, current_tag
refers to an instance of property
, not an instance of SharedAttribute
.
__set_name__
is called after the class has been defined (so that the class can be passed as the owner
argument), not immediately after the assignment is made.
However, you changed the value of current_tag
to be a property
, so the name is no longer bound to a SharedAttribute
instance once the class definition has completed.'
From the documentation (emphasis mine):
Automatically called at the time the owning class owner is created.
The SharedAttribute
instance is created while the body of the class
statement is being executed. The class itself is not created until after the body is executed; the result of executing the body is a namespace which is passed as an argument to the metaclass that creates the class. During that process, the class attributes are scanned for values with a __set_name__
method, and only then is the method called.
Here's a simpler example:
class GitFetcher:
current_branch = SharedAttribute()
current_tag = SharedAttribute()
current_tag = 3
By the time GitFetcher
is defined, current_tag
is no longer bound to a descriptor, so no attempt to call current_tag.__set_name__
is made.
It's not clear if you want to somehow compose a property with a SharedAttribute
, or if this is just an inadvertent re-use of the name current_tag
.