[Python-Dev] Change in evaluation order in new object model
Tim Peters
tim.one@home.com
Fri, 2 Nov 2001 13:32:19 -0500
[Michael McLay]
> I was suprised by a change to the order of evaluation of members
> in the new object type. I haven't found an explanation for why the
> change was made.
Read PEP 252, paying special attention to the section containing:
When a dynamic attribute (one defined in a regular object's
__dict__) has the same name as a static attribute (one defined
by a meta-object in the inheritance graph rooted at the regular
object's __class__), the static attribute has precedence if it
is a descriptor that defines a __set__ method (see below);
otherwise (if there is no __set__ method) the dynamic attribute
has precedence. In other words, for data attributes (those
with a __set__ method), the static definition overrides the
dynamic definition, but for other attributes, dynamic overrides
static.
Rationale: we can't have a simple rule like "static overrides
dynamic" or "dynamic overrides static", because ...
> ...
> With the new slots mechanism the order has been reversed. The
> class level dictionary is searched and then the slots are evaluated.
I should hope so! The *point* of __slots__ (which is what you're really
talking about, not the general concept of "slots") is that the class, not
the object, is responsible for doing the attribute name->storage_address
mapping, and in intended use an object of a class with __slots__ doesn't
even have a __dict__ (each __slot__ attribute is allocated at a fixed offset
from the start of the object, saving tons of storage).
>>> class C(object):
... __slots__ = ['a']
Now objects of type C don't have a dict: storage for one attribute 'a' is
allocated directly in C objects.
>>> c = C()
>>> c.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object has no attribute '__dict__'
You can set and get 'a':
>>> c.a = 12
>>> c.a
12
C.a is a special beast:
>>> C.a
<member 'a' of 'C' objects>
What makes it special isn't that it came from __slots__, though, but that it
has a __set__ method (reread the quoted text above until your eyes bleed
<wink>: this is a deadly simple protocol, so simple that it can be hard to
understand at first (shades of the metaclass hook and continuations,
there)):
>>> dir(C.a)
['__class__', '__delattr__', '__doc__', '__get__', '__getattribute__',
^^^^^^^
C.a.__get__ is called when c.a is referenced.
'__hash__', '__init__', '__name__', '__new__', '__objclass__',
'__reduce__', '__repr__', '__set__', '__setattr__', '__str__']
^^^^^^^
C.a.__set__ is called when c.a is bound or del'ed.
Objects of C type can't grow new attributes:
>>> c.b =12
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object has no attribute 'b'
> >>> class B(object):
> __slots__ = ['a','b','c']
>
> >>> b = B()
> >>> b.a = 4
> >>> b.a
> 4
> >>> B.a = 6
Here you overwrote the descriptor that allows b.a to mean something sensible
(you nuked the <member 'a' of 'B' objects> thingie that maps 'a' to its
storage address). Now B.a is an ordinary class attribute, and remember that
b doesn't have a __dict__ (which you asked for, by using __slots__; you're
not required to use __slots__).
> >>> b.a
> 6
> >>> b.a = 8
> Traceback (most recent call last):
> File "<pyshell#61>", line 1, in ?
> b.a = 8
> AttributeError: 'B' object attribute 'a' is read-only
I agree it's an odd msg, but I'm not sure it can do better easily: by
overwriting B.a (which was nuts -- you're exploring pathologies here, not
intended usage), you've left b as an object with an 'a' attribute inherited
from its class, but also as an object that can't grow new attributes of its
own. Python looks at "hmm, I *can't* set 'a', but I do *have* an 'a'", and
comes up with "read-only".
Try your example again without using __slots__ (you do *not* want __slots__
if you intend an object's namespace to be dynamic -- __slots__ announces
that you guarantee the set of object attributes is fixed at class creation
time):
>>> class B(object): pass
...
>>> b = B()
>>> b.a = 4
>>> B.a = 6
>>> b.a
4
>>> b.a = 8
>>> b.a
8
>>> B.a
6
>>>
IOW, don't use new features if you don't want new semantics, and things look
much the same. If you want __slots__, though, there was no way to get its
effect prior to 2.2 short of writing an ExtensionClass in C.