properties and inheritance

N

Neal D. Becker

It seems that properties, as implemented in 2.3, don't behave as I'd expect
with respect to inheritance:

class A (object):
def f(self):
print "A"

doF = property (fget=f)

class B(A):
def f(self):
print "B"


b = B()
b.doF : prints "A"

Is there a rationale for this? I would have thought that if properties are
a shorthand for calling functions, and functions are polymorphic, then why
are properties not? Doesn't that make properties behaviour inconsistent
with other attributes?
 
A

Alex Martelli

Neal D. Becker said:
It seems that properties, as implemented in 2.3, don't behave as I'd expect
with respect to inheritance:

class A (object):
def f(self):
print "A"

doF = property (fget=f)

class B(A):
def f(self):
print "B"

b = B()
b.doF : prints "A"

Is there a rationale for this?

Object 'doF' (of type 'property', held in A.__dict__) holds as its
'fget' attribute just what you passed to it -- the 'f' function that's
held in A.__dict__. IOW,
A.doF.fget is A.__dict__['f']
I would have thought that if properties are
a shorthand for calling functions,

They are -- that A.doF.fget is indeed a function, and the __get__ method
of the property calls the function in question. *The function*, not any
other function of the same name that might happen to be elsewhere...
and functions are polymorphic, then why
are properties not?

Functions are descriptors and so are properties -- both have __get__
methods. It's not an issue of polymorphism: it's an issue of early vs
late binding. What function a property holds (and thus calls) is bound
pretty early, when the property object is created; what attribute an
access such as b.f would get (and there is no such access going on here)
is bound very late, at the time the access is executed.
Doesn't that make properties behaviour inconsistent
with other attributes?

Not particularly. B subclasses A, A has an attribute named 'doF' which
B doesn't override (nor does instance b of B), so the access b.doF uses
A.doF.__get__(b, B). Since object A.doF is holding a function object
(not a name -- it doesn't do any getattr, therefore), it call the
function object it holds, not another. You'd have exactly the same
effect going on if you coded, say:

class Foo:
def bar(self): return 'Foo'
def callbar(self, bar=bar): return bar(self)

class Bar(Foo):
def bar(self): return 'Bar'

b = Bar()
print b.callbar()

No inconsistency between behavior of function callbar and behavior of a
property with bar as its fget -- both bind early, when coded this way.

If you want more indirectness it's not hard to obtain it, either by
having the property hold a function that does some further attribute
lookup, or by using instead of property some custom descriptor type,
call it indirect_property, which holds a name and looks it up. The
extra lookup at each access will of course make things slower, but if
you do need the indirection then that may be acceptable. I consider it
a reasonable design decision to make the leaner, faster descriptor the
built-in one, personally.


Alex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,140
Latest member
SweetcalmCBDreview
Top