Python @property inheritance the right way explains how to call the parent setter.
class Number:
def __init__(self):
self._value = None
@property
def value(self):
assert self._value is not None
return self._value
@value.setter
def value(self, new_value):
self._value = new_value
class Integer(Number):
@property
def value(self):
return super().value
@value.setter
def value(self, new_value):
_value = int(new_value)
super(Integer, type(self)).value.fset(self, _value) # <----- OK with using type(self)
# super(Integer, self).value.fset(self, _value) # <----- Assert error with self
i = Integer()
i.value = 1 # cause assertion error with "super(Integer, self)"
print(i.value)
With super(Integer, type(self)).value.fset(self, _value)
, i.value = 1
invokes the setter as expected.
With super(Integer, self).value.fset(self, _value)
, i.value = 1
invokes the getter instead of the setter, hence causing the assertion error.
AssertionError Traceback (most recent call last)
<ipython-input-8-2c57a07c128d> in <module>
35
36 i = Integer()
---> 37 i.value = 1
38 print(i.value)
<ipython-input-8-2c57a07c128d> in value(self, new_value)
32 _value = int(new_value)
33 #super(Integer, type(self)).value.fset(self, _value)
---> 34 super(Integer, self).value.fset(self, _value)
35
36 i = Integer()
<ipython-input-8-2c57a07c128d> in value(self)
10 @property
11 def value(self):
---> 12 assert self._value is not None
13 return self._value
Please help understand why super(Integer, self).value.fset(self, _value)
goes to the getter instead of the setter although calling fset
. Reading the documents and articles, it looks to me passing the object self
instead of type/class type(self)
is the correct way to access the method bound to the instance itself, but it does not work.
super([type[, object-or-type]])
The object-or-type determines the method resolution order to be searched. The search starts from the class right after the type.
For example, if mro of object-or-type is D -> B -> C -> A -> object and the value of type is B, then super() searches C -> A -> object.
The mro attribute of the object-or-type lists the method resolution search order used by both getattr() and super(). The attribute is dynamic and can change whenever the inheritance hierarchy is updated.
Supercharge Your Classes With Python super()
In Python 3, the super(Square, self) call is equivalent to the parameterless super() call. The first parameter refers to the subclass Square, while the second parameter refers to a Square object which, in this case, is self. You can call super() with other classes as well:
def surface_area(self): face_area = super(Square, self).area() return face_area * 6 def volume(self): face_area = super(Square, self).area() return face_area * self.length
What about the second parameter? Remember, this is an object that is an instance of the class used as the first parameter. For an example, isinstance(Cube, Square) must return True.
By including an instantiated object, super() returns a bound method: a method that is bound to the object, which gives the method the object’s context such as any instance attributes. If this parameter is not included, the method returned is just a function, unassociated with an object’s context.
The problem with super(Integer, self).value.fset(self, _value)
(or the simpler equivalent, super().value.fset(self, _value)
) occurs before you even get to the fset
. The descriptor protocol is engaged on all lookups on an instance, cause it to invoke the getter simply by doing super(Integer, self).value
(or super().value
). That's why your inherited getter works in the first place; it invoked the property
descriptor, and got the value produced by it.
In order to bypass the descriptor protocol (more precisely, move from instance to class level invocation, where property
s do nothing special in the class level scenario), you need to perform the lookup on the class itself, not an instance of it. super(Integer, type(self))
invokes the form of super
that returns a super
object bound at the class level, not the instance level, allowing you to retrieve the raw descriptor itself, rather than invoking the descriptor and getting the value it produces. Once you have the raw descriptor, you can access and invoke the fset
function attached to it.
This is the same issue you have when super
isn't involved. If you have an instance of Number
, and want to directly access the fset
function (rather than invoking it implicitly via assignment), you have to do:
num = Number()
type(num).value.fset(num, 1)
because doing:
num.value.fset(num, 1)
fails when you retrieve num.value
(getting the None
the getter produces), then try to look up fset
on None
.