I was reading about python descriptors and how to use them. I came across an example from the python official doc,
import logging
logging.basicConfig(level=logging.INFO)
class LoggedAgeAccess:
def __get__(self, obj, objtype=None):
value = obj._age
logging.info('Accessing %r giving %r', 'age', value)
return value
def __set__(self, obj, value):
logging.info('Updating %r to %r', 'age', value)
obj._age = value
class Person:
age = LoggedAgeAccess() # Descriptor instance
def __init__(self, name, age):
self.name = name # Regular instance attribute
self.age = age # Calls __set__()
def birthday(self):
self.age += 1 # Calls both __get__() and __set__()
Here, we are using '_age' as the attribute name in the descriptor instead of 'age' for storing it in the object's dictionary.
The code works fine if, I do
mary = Person('Mary M', 30)
print(vars(mary))
The output is,
INFO:root:Updating 'age' to 30
{'name': 'Mary M', '_age': 30}
When I tried to change the attribute name in the descriptor from '_age' to 'age'. The __set__ function runs continuosly in an infinite loop. (Class Person remaining the same)
For instance,
class LoggedAgeAccess:
def __get__(self, obj, objtype=None):
value = obj.age
logging.info('Accessing %r giving %r', 'age', value)
return value
def __set__(self, obj, value):
logging.info('Updating %r to %r', 'age', value)
obj.age = value
The output that I got by running,
mary = Person('Mary M', 30)
print(vars(mary))
is, an infinite loop of the log in __set__ function,
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
INFO:root:Updating 'age' to 30
It appears from the output, that we cannot have same name, for attribute of the object in class and the attribute of the same object in descriptor. Why so?
Also, is it related to instance of descriptor having the same name?
Because obj.age = whatever
invokes the descriptor protocol, which will eventually call __set__
!
def __set__(self, obj, value):
logging.info('Updating %r to %r', 'age', value)
obj.age = value # this calls `Logging.__set__` recursively