I have a following (simplified of course) descriptor:
class d:
def __init__(self, method):
self.method = method
def __get__(self, instance, owner=None):
print(instance, owner, self.method)
return self.method(instance)
In __get__()
I want to access the class where the decorated function is defined, but the owner
argument is B
on both invocations of __get__()
that happen in the following code:
class A:
@d
def f(self):
return "A"
class B(A):
@d
def f(self):
return super().f + "B"
print(B().f)
I've checked the Descriptor HowTo Guide section on calling descriptors via super()
and it says that this invocation indeed passes the subclass type to the parent class __get__()
. Does it suggest I may need to define __getattribute__()
to get what I want, or is there a different way? I understand that the super()
call doesn't just return A
but a proxy for B
but I feel like there should be a way to get A
in the descriptor.
I'll also appreciate a clearer explanation of what is happening in my code.
The easier thing to do is to record, in the descriptor, the class where it is defined at class creation time.
Since Python 3.6 that is possible due to the addition of the __set_name__
method to the descriptor protocol.
The owner
parameter received in __set_name__
is the actuall class where the descriptor is defined. It can then be set as a descriptor attribute:
class d:
def __init__(self, method):
self.method = method
def __set_name__(self, owner, name):
self.name = name # should be the same as self.method.__name__
self.owner = owner
def __get__(self, instance, owner=None):
print(f"{instance=}, {owner=}, {self.owner=}, {self.method=}")
return self.method(instance)
And running this along with your example A
and B
:
(env311) [gwidion@fedora tmp01]$ python script.py
instance=<__main__.B object at 0x7f78c6094110>, owner=<class '__main__.B'>, self.owner=<class '__main__.B'>, self.method=<function B.f at 0x7f78c607d800>
instance=<__main__.B object at 0x7f78c6094110>, owner=<class '__main__.B'>, self.owner=<class '__main__.A'>, self.method=<function A.f at 0x7f78c607d760>
AB
Without the resort to __set_name__
the thing to do would be indeed to walk linearly through the __mro__
until it would find self
, as long as it had not been shadowed by another decorator of the same kind:
def __get__(self, instance, owner=None):
for cls in owner.__mro__:
if self in cls.__dict__.values():
defined_at = cls
break
else:
raise RuntimeError()
print(f"{instance=}, {owner=}, {self.defined_at=}, {self.method=}")
return self.method(instance)