Reputation: 45291
If I have a parent class and a child class, I get the following behavior in Python when asking for attributes:
class Parent():
i = 1
class Child(Parent):
def __init__(self, j = 2):
self.j = j
ch = Child()
print(ch.i, ch.j) # 1 2
The request for the i
and j
attributes go up the MRO chain as expected; i
is found in the parent class attributes, j
is found in the instance attributes.
Now, if I add a generic descriptor and replace the j
attribute in Child
, this happens:
class _Attr():
def __init__(self, attr_name):
self.attr_name = '_' + attr_name
def __get__(self, instance, klass):
return getattr(instance, self.attr_name)
class Child(Parent):
j = _Attr('j')
def __init__(self, j = 2):
self._j = j
ch = Child()
print(ch.i, ch.j) # 1 2
So far, so good.
However, using the above descriptor, if we do something like this:
class Child(Parent):
j = _Attr('j')
i = _Attr('i')
def __init__(self, j = 2):
self._j = j
ch = Child()
print(ch.i, ch.j) # AttributeError: 'Ch" object has no attribute '_i'
This error occurs because of this failed attribute lookup:
return getattr(ch, '_i')
What I want is for the descriptor to "fail silently" and for the attribute lookup to continue up the MRO chain. I'm unsure how to do this.
I tried this:
class _Attr():
def __init__(self, attr_name):
self.attr_name = '_' + attr_name
def __get__(self, instance, klass):
result = getattr(instance, self.attr_name, None)
if result == None:
return NotImplemented
else:
return result
But that does not do the trick. How can I get the behavior I want? I have a feeling I need to use super()
somehow in this case, but I don't know what to do with it.
Upvotes: 1
Views: 143
Reputation: 239643
Two things.
you need to store the reference to the actual attribute name in the _Attr
, so that you can use that in the parent lookup.
during the lookup, you can delegate the attribute fetching work to the Parent
class, with super(klass, instance)
So your _Attr
would look like this
class _Attr():
def __init__(self, attr_name):
self._original_name = attr_name
self.attr_name = '_' + attr_name
def __get__(self, instance, klass):
if hasattr(instance, self.attr_name):
return getattr(instance, self.attr_name)
return getattr(super(klass, instance), self._original_name)
Upvotes: 1
Reputation: 21867
You can take care of setting _i in the init method as follows.
class Child(P):
j = _Attr('j')
i = _Attr('i')
def __init__(self, j = 2):
self._j = j
self._i = P.i
ch = Child()
print(ch._i, ch._j)
Upvotes: 1