In the following example:
class Desc:
def __get__(self, object, type=None):
print('Descriptor Desc read-mode activated.')
return object._essence
def __set__(self, object, value):
print('Descriptor Desc write-mode activated.')
object._essence = value
class C:
essence = Desc()
class D(C):
#essence = 'MAGNESIUM'
def __init__(s):
s.essence = 'STEAM'
print('Class D object initialized.\n')
o = D()
print(o.__dict__)
print(o.essence)
o.essence = 'ALUMINUM'
print(o.essence)
The output is as follows (Sublime Text):
Descriptor Desc write-mode activated.
Class D object initialized.
{'_essence': 'STEAM'}
Descriptor Desc read-mode activated.
STEAM
Descriptor Desc write-mode activated.
Descriptor Desc read-mode activated.
ALUMINUM
[Finished in 176ms]
, which is as I expect. But when the class variable in class D is enabled (by un-hashing that line), the descriptor is no longer reached and the class variable is looked-up instead. Then the output becomes:
Class D object initialized.
{'essence': 'STEAM'}
STEAM
ALUMINUM
[Finished in 178ms]
Shouldn't the data-descriptor in class C take precedence over all other, even if it's higher up the inheritance chain?
CodePudding user response:
Shouldn't the data-descriptor in class C take precedence over all other, even if it's higher up the inheritance chain?
No, since
class D(C):
essence = 'MAGNESIUM'
creates a name D.essence
(on the class, not on instances), and since that name is a thing, the superclass chain is not followed to find C.essence
(which is a descriptor).
CodePudding user response:
You ask:
Shouldn't the data-descriptor in class C take precedence over all other, even if it's higher up the inheritance chain?
And maybe that's because you read the documentation and found:
Data descriptors always override instance dictionaries.
And also:
Instance lookup scans through a chain of namespaces giving data descriptors the highest priority, followed by instance variables, then non-data descriptors, then class variables, and lastly getattr() if it is provided.
However, your definition of D
:
class D(C):
essence = 'MAGNESIUM'
Overrides the definition of the class attribute defined on super-class C as essence = Desc()
with essence
as a simple class attribute with a string value. It no longer is a descriptor and assigning or reading won't reach your code.
Consider:
class Desc:
def __get__(self, object, type=None):
print('Descriptor Desc read-mode activated.')
return object._essence
def __set__(self, object, value):
print('Descriptor Desc write-mode activated.')
object._essence = value
class C:
essence = Desc()
class D(C):
# essence = 'MAGNESIUM'
def __init__(s):
s.essence = 'STEAM'
print('Class D object initialized.\n')
d = D()
# show all the classes in the method resolution order that have `essence`
essence_found = [c for c in type(d).__mro__ if 'essence' in c.__dict__]
print([c.__name__ for c in essence_found])
# is `.essence` on the first one that was found a descriptor?
print('__get__' in type(essence_found[0].__dict__['essence']).__dict__)
class D(C):
essence = 'MAGNESIUM'
def __init__(s):
s.essence = 'STEAM'
print('Class D object initialized.\n')
d = D()
essence_found = [c for c in type(d).__mro__ if 'essence' in c.__dict__]
print([c.__name__ for c in essence_found])
print('__get__' in type(essence_found[0].__dict__['essence']).__dict__)
# here the second one is, but it doesn't matter:
print('__get__' in type(essence_found[1].__dict__['essence']).__dict__)
Output:
Descriptor Desc write-mode activated.
Class D object initialized.
['C']
True
Class D object initialized.
['D', 'C']
False
True
For the second definition of D
, with the redefined class attributes, the method resolution order shows essence
on D
, so there's no need to continue to C
. But the essence
on that class is not a descriptor.
A descriptor is given the highest priority if it is found, but if the first class in the chain of namespaces has the required attribute. It is not a descriptor, but that doesn't matter. It doesn't say it will search all the way through the chain of namespaces for anything that defines the attribute as a descriptor, and then repeat the search for the next best thing. It says it will find it in the chain of namespaces, and prefer a descriptor over anything else on the first one it finds.
Edit: I ended up rewriting the test that shows what is going on, but am not sure whether it's clearer or not:
d = D()
# show all the classes in the method resolution order that have `essence`
# is `.essence` on the first one that was found a descriptor?
# for the second one it is, but it doesn't matter:
print([
(c.__name__, '__get__' in type(c.__dict__['essence']).__dict__)
for c in type(d).__mro__ if 'essence' in c.__dict__
])
Output:
Descriptor Desc write-mode activated.
Class D object initialized.
[('C', True)]
Class D object initialized.
[('D', False), ('C', True)]