properties and inheritance

Alex Martelli aleaxit at yahoo.com
Thu Oct 7 07:57:00 EDT 2004


Neal D. Becker <ndbecker2 at verizon.net> wrote:

> 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



More information about the Python-list mailing list