While trying to create descriptors a few different ways I noticed some strange behavior that I'm trying to understand. Below are the three different ways I have gone about creating descriptors:
>>> class NumericValueOne():
... def __init__(self, name):
... self.name = name
... def __get__(self, obj, type=None) -> object:
... return obj.__dict__.get(self.name) or 0
... def __set__(self, obj, value) -> None:
... obj.__dict__[self.name] = value
>>> class NumericValueTwo():
... def __init__(self, name):
... self.name = name
... self.internal_name = '_' + self.name
... def __get__(self, obj, type=None) -> object:
... return getattr(obj, self.internal_name, 0)
... def __set__(self, obj, value) -> None:
... setattr(obj, self.internal_name, value)
>>> class NumericValueThree():
... def __init__(self, name):
... self.name = name
... def __get__(self, obj, type=None) -> object:
... return getattr(obj, self.name, 0)
... def __set__(self, obj, value) -> None:
... setattr(obj, self.name, value)
I then use them in the Foo
classes, like below:
>>> class FooOne():
... number = NumericValueOne("number")
>>> class FooTwo():
... number = NumericValueTwo("number")
>>> class FooThree():
... number = NumericValueThree("number")
my_foo_object_one = FooOne()
my_foo_object_two = FooTwo()
my_foo_object_three = FooThree()
my_foo_object_one.number = 3
my_foo_object_two.number = 3
my_foo_object_three.number = 3
While FooOne
and FooTwo
work as expected when both setting & getting values. FooThree
throws the following error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __set__
File "<stdin>", line 7, in __set__
File "<stdin>", line 7, in __set__
[Previous line repeated 497 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object
It looks like setattr()
is calling the __set__()
method? But why should it be doing that if setattr()
is modifying the obj
__dict__
? And why does this work if we use internal_name
?
Why is that we NEED to use a private variable in order to use the built-in getattr()
and setattr()
methods correctly? Also, how is this different from just directly modifying the obj
__dict__
like in NumericValueOne
?
But why should it be doing that if
setattr()
is modifying theobj
__dict__
?
setattr
doesn't just modify the __dict__
. It sets attributes, exactly like x.y = z
would, and for the attribute you're trying to set, "set this attribute" means "call the setter you're already in". Hence, infinite recursion.
And why does this work if we use
internal_name
?
That name doesn't correspond to a property, so it just gets a __dict__
entry.