Add Properties to Instances?

Martin Miller ggrp1.20.martineau at dfgh.net
Sat Mar 19 10:25:40 EST 2005


Bengt Richter wrote:
> On 14 Mar 2005 13:07:29 -0800, "Martin Miller"
<ggrp1.20.martineau at dfgh.net> wrote:
>
> >Bengt Richter wrote, in part:
> >> On 14 Mar 2005 01:19:23 -0800, "Martin Miller"
> ><ggrp1.20.martineau at dfgh.net>
> >> wrote, in part:
> >> >What still puzzles me, though, is why all the above to make
> >properties
> >> >work on instances is necessary in the first place. It's certainly
> >not
> >> >clear (to me) from what is said in the How-to at:
> >> >>
> >>
>
>>http://users.rcn.com/python/download/Descriptor.htm#invoking-descriptors
> >> >I suspect that it may be simply a performance issue, in other
words,
> >it
> >> >was considered too slow to check for instance
property/discriptors
> >--
> >> >although *why* is not clear to me.
> >>
> >> I suspect the desired semantics re precedence have evolved to make
> >normal
> >> programs easily implementable, and that probably drove the
> >implementation:
> >> """
> >> For objects, the machinery is in object.__getattribute__ which
> >transforms
> >> b.x into type(b).__dict__['x'].__get__(b, type(b)).
> >> The implementation works through a precedence chain that gives (my
> >added
> >> [1,2,3])
> >>
> >> [1] data descriptors priority over instance variables,
> >> [2] instance variables priority over non-data descriptors,
> >> [3] and assigns lowest priority to __getattr__ if provided.
> >>
> >> The full C implementation can be found in
PyObject_GenericGetAttr()
> >in
> >> Objects/object.c.
> >> """
> >
> >I haven't examined the C code in Objects/object.c to see *how* the
> >semantics are implemented because that's not really the
point...which
> >is the descriptions of what's suppose to happen don't seem to match
> >what actually does. To illustrate, consider:
> >> class Foobar(object):
> >>     pass
> >>
> >> def myget(self, obj, type=None):
> >>     return 42
> >>
> >> def myset(self, value):
> >>     raise AttributeError("this is a read-only property")
> >>
> >> foobar = Foobar()
> >> foobar.x = property(myget, myset)
> >>
> >> print "foobar.x:", foobar.x
> >
> >Which prints:
> >> foobar.x: <property object at 0x00AE5850>
> >
> >Ignoring "why" issue, my question becomes:
> >
> >foobar.x is a data descriptor property, however
object.__getattribute__
> It is a property, and it has the requisite methods (__get__ and
__set__) to be
> a data descriptor, but it is not a data descriptor unless it is
visible as
> an attribute of type(foobar), which foobar.x is not.
>
> >does *not* seem to be treating it as such and handling it the way
> >described either by you or in what is written in the how-to.
> >Specifically the statements that:
> >> For objects, the machinery is in object.__getattribute__ which
> >transforms
> >> b.x into type(b).__dict__['x'].__get__(b, type(b)).
> >
> >This doesn't seem to be occuring. Am I missing something?
> >
> I think so. Following your example:
>
>  >>> class Foobar(object):
>  ...     pass
>  ...
>  >>> def myget(self, obj, type=None):
>  ...     return 42
>  ...
>  >>> def myset(self, value):
>  ...     raise AttributeError("this is a read-only property")
>  ...
>  >>> foobar = Foobar()
>  >>> foobar.x = property(myget, myset)
>  >>> print "foobar.x:", foobar.x
>  foobar.x: <property object at 0x02EF0644>
>  >>>
>  >>> type(foobar).__dict__['x']
>  Traceback (most recent call last):
>    File "<stdin>", line 1, in ?
>  KeyError: 'x'
>
> meaning
>    b.x into type(b).__dict__['x'].__get__(b, type(b)).
> does not apply, and the attempt to get foobar.x falls back to
> ordinary attribute access. I.e., foobar.x will be whatever
>
> BTW, I'm not sure type(b).__dict__['x'].__get__(b, type(b))
> is really fully correct, since that would not find foobar.x in a
Foobar base class:
>
>  >>> class Base(object): pass
>  ...
>  >>> Base.x = property(myget, myset)
>  >>> class Sub(Base): pass
>  ...
>  >>> sub = Sub()
>  >>> sub.x
>  Traceback (most recent call last):
>    File "<stdin>", line 1, in ?
>  TypeError: myget() takes at least 2 arguments (1 given)
>
> (At least it was found, even if the signature is bad ;-)
>
> But:
>
>  >>> type(sub).__dict__['x']
>  Traceback (most recent call last):
>    File "<stdin>", line 1, in ?
>  KeyError: 'x'
>
> So the real truth chases down the mro chain before giving up
> and falling back to ordinary attribute access, IWT.
>
> Regards,
> Bengt Richter

Ah, from your comments and explanation I think I finally understand
that the description in the docs *is* in fact accurate -- and
indirectly, the reason for properties on instances not working by
default. Specifically I was not realizing that the "type(b).__dict__"
in the statement:
>  b.x is tranformed into type(b).__dict__['x'].__get__(b, type(b))
implys that no check for 'x' in made in "b.__dict__".

Now I'm back to wondering "why" because it would be relatively easy to
check for a descriptor in the instance dictionary first -- and would be
more "Pythonic", IMHO, since instances can generally augment (or
override) that which is defined by their class.

Thank you for bearing with me on this somewhat obtuse follow-on
question regarding properties and descriptors (and to the everyone else
for their suggestions for work-arounds).

Best regards,
Martin

P.S. After looking at the implementation of PyObject_GenericGetAttr()
in Objects/object.c, I believe your observation about the
"type(b).__dict__['x'].__get__(b, type(b))" not being fully correct is
true. Instead, there appears to be a search being done through the mro
base classes of type(b). Perhaps that fact could be used to create
"mix-in" style classes with the desired properties...




More information about the Python-list mailing list