Inconsistent behavior of descriptors

Bengt Richter bokr at oz.net
Wed Oct 1 08:26:54 EDT 2003


On Tue, 30 Sep 2003 20:15:10 +0400 (MSD), "Denis S. Otkidach" <ods at strana.ru> wrote:

>
>I've noticed that the order of attribute lookup is inconsistent
>when descriptor is used.  property instance takes precedence of
>instance attributes:
>
>>>> class A(object):
>...     def _get_attr(self):
>...         return self._attr
>...     attr = property(_get_attr)
>...
>>>> a=A()
>>>> a.__dict__
>{}
>>>> a.__dict__['attr']=1
>>>> a.__dict__
>{'attr': 1}
>>>> a.attr
>Traceback (most recent call last):
>  File "<stdin>", line 1, in ?
>  File "<stdin>", line 3, in _get_attr
>AttributeError: 'A' object has no attribute '_attr'
>
>But it doesn't when I use custom class of descriptor:
>
>>>> class descr(object):
>...     def __get__(self, inst, cls):
>...         return inst._attr
>...
>>>> class B(object):
>...     attr = descr()
>...
>>>> b=B()
>>>> b.__dict__
>{}
>>>> b.__dict__['attr']=1
>>>> b.__dict__
>{'attr': 1}
>>>> b.attr
>1
>
>Subclasses of property behave like property itself:
>
>>>> class descr2(property):
>...     def __get__(self, inst, cls):
>...         return inst._attr
>...
>>>> class C(object):
>...     attr = descr2()
>...
>>>> c=C()
>>>> c.__dict__
>{}
>>>> c.__dict__['attr']=1
>>>> c.__dict__
>{'attr': 1}
>>>> c.attr
>Traceback (most recent call last):
>  File "<stdin>", line 1, in ?
>  File "<stdin>", line 3, in __get__
>AttributeError: 'C' object has no attribute '_attr'
>
>Is it an undocumented feature or I have to submit a bug report?
>
I think (not having read the above carefully) that it's all documented in

    http://users.rcn.com/python/download/Descriptor.htm

(thanks to Raymond Hettinger).

excerpt:
"""
    The details of invocation depend on whether obj is an object or a class.
    Either way, descriptors only work for  new style objects and classes.  A
    class is new style if it is a subclass of object.

    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   data
    descriptors  priority  over   instance  variables,  instance   variables
    priority over  non-data  descriptors,  and assigns  lowest  priority  to
    __getattr__ if  provided. The  full  C implementation  can be  found  in
    PyObject_GenericGetAttr() in Objects/object.c.
    
    For classes, the machinery is in type.__getattribute__ which  transforms
    B.x into  B.__dict__['x'].__get__(None, B).  In  pure Python,  it  looks
    like:
    
    def __getattribute__(self, key):
        "Emulate type_getattro() in Objects/typeobject.c"
        v = object.__getattribute__(self, key)
        if hasattr(v, '__get__'):
           return v.__get__(None, self)
        return v

The important points to remember are:

     descriptors are invoked by the __getattribute__ method 
     overriding __getattribute__ prevents automatic descriptor calls 
     __getattribute__ is only available with new style classes and objects 
     object.__getattribute__ and type.__getattribute__ make different calls to __get__. 
     data descriptors always override instance dictionaries. 
     non-data descriptors may be overridden by instance dictionaries. 
"""

Regards,
Bengt Richter




More information about the Python-list mailing list