In the book, Python in a Nutshell,
the authors claim the following code snippet is problematic:
class B:
def f(self):
return 23
g = property(f)
class C(B):
def f(self):
return 42
c = C()
print(c.g) # prints: 23, not 42
And the solution, as the authors claimed, is to redirect the calculation of property c.g
to the class C
's implementation of f
at the superclass B
's level.
class B:
def f(self):
return 23
def _f_getter(self):
return self.f()
g = property(_f_getter)
class C(B):
def f(self):
return 42
c = C()
print(c.g) # prints: 42, as expected
I disagree but please help me to understand why the authors claimed as they did.
Two reasons for my disagreement.
Superclasses are far likely to be in upstream modules and it is not ideal for upstream modules to implement additional indirection to take into account that subclasses in downstream modules may reimplement the getter method for the superclass property.
The calculation of a property of a superclass is done after the instantiation of an instance of the superclass. An instance of the subclass must be instantiated after the instantiation of an instance of the superclass. Therefore, if the writer of C does not explicitly "declare"
C.g
to be a property with a different implementationC.f
, then it should rightly inherits the propertyB.g
and i.e.c.g
should beb.g
.
My question is:
am I right with this thought or are the authors right with their claims?
CodePudding user response:
I don't know about property
but why don't you use:
class B:
def f(self):
return 23
@property
def g(self):
return self.f()
class C(B):
def f(self):
return 42
c = C()
print(c.g)
print(type(c.g))
# Output
42
int
It's not the same as your initial code?
CodePudding user response:
This is a slight more in depth explanation of what is happening.
Basically in your first snippet (which is not very idiomatic python), you bind the exising B.f
method to B.g
, when C
inherits g
, its still bound to B.f
. g
is basically statically linked to B.f
.
class B:
def f(self):
return 23
g = property(f) # B.f gets bound to B.g as property
class C(B):
def f(self):
return 42
# C.g not redeclared is still bound to B.f
The "correction snippet" is even less idiomatic, and bind a method which will retrieve the f
method from self
, the instance it is run on, which makes it dynamic because if self
's f
method is different from B.f
, then the result changes. However we still bind statically to a private method.
class B:
def f(self):
return 23
def _f_getter(self):
return self.f()
g = property(_f_getter) # B._f_getter gets bound to B.g, but B._f_getter uses the f method of self
class C(B):
def f(self):
return 42
# C.g is still bound to B._f_getter but the f method of self has changed
This is the idiomatic way of using property
, as a decorator around another method. We can use the same principle as above, but rather than declaring a private intermediate method, we can use g
as a name directly.
class B:
def f(self):
return 23
@property
def g(self): # no binding the B.g property uses f method of self directly
return self.f()
class C(B):
def f(self):
return 42
# C.g uses f method of self which has changed
CodePudding user response:
"The calculation of a property of a superclass is done after the instantiation of an instance of the superclass. " - NO, the property is declared along with a class declaration and it's tied to a target function in that class.
print(vars(B)['g']) # <property object at 0x7f5f799ebce0>
"An instance of the subclass must be instantiated after the instantiation of an instance of the superclass." - NO again. That's obvious. Declaration and instantiation are different processes.
"Therefore, if the writer of C
does not explicitly "declare" C.g
to be a property with a different implementation C.f
, then it should rightly inherits the property B.g
" - It inherits g
property object in its state on declaration phase (i.e. tied to B.f
function).
So, you naturally have valid options:
- to override a property in your subclass
- or to declare your initial property object to consider inheritance, which is no bad
class B:
def f(self):
return 23
g = property(lambda self: self.f())
class C(B):
def f(self):
return 42
c = C()
print(c.g) # 42