Inconsistent behavior of descriptors

D

Denis S. Otkidach

I've noticed that the order of attribute lookup is inconsistent
when descriptor is used. property instance takes precedence of
instance attributes:
.... 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:
.... def __get__(self, inst, cls):
.... return inst._attr
........ attr = descr()
....
b=B()
b.__dict__ {}
b.__dict__['attr']=1
b.__dict__ {'attr': 1}
b.attr
1

Subclasses of property behave like property itself:
.... def __get__(self, inst, cls):
.... return inst._attr
........ 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?
 
J

John Roth

Denis S. Otkidach said:
I've noticed that the order of attribute lookup is inconsistent
when descriptor is used. property instance takes precedence of
instance attributes:
... 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:
... def __get__(self, inst, cls):
... return inst._attr
...... attr = descr()
...
b=B()
b.__dict__ {}
b.__dict__['attr']=1
b.__dict__ {'attr': 1}
b.attr
1

Subclasses of property behave like property itself:
... def __get__(self, inst, cls):
... return inst._attr
...... 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'm not entirely sure what you're describing, however, I
notice that your descriptor object has a __get__ method,
but no __set__ or __del__ methods. The lookup chain
is quite different for descriptors with only __get__ methods
than it is for descriptors for all three.

In particular, it's intended that instance attributes should
be ignored if there is a data descriptor (that is, a descriptor
with both__get__ and __set__ methods.)

I'm not sure if this applies to your problem.

John Roth
 
B

Bengt Richter

I've noticed that the order of attribute lookup is inconsistent
when descriptor is used. property instance takes precedence of
instance attributes:
... 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:
... def __get__(self, inst, cls):
... return inst._attr
...... attr = descr()
...
b=B()
b.__dict__ {}
b.__dict__['attr']=1
b.__dict__ {'attr': 1}
b.attr
1

Subclasses of property behave like property itself:
... def __get__(self, inst, cls):
... return inst._attr
...... 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
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top