Unexpected behavior of read only attributes and super
Samuel M. Smith
smithsm at samuelsmith.org
Thu Dec 8 15:26:47 EST 2005
>
>
> P.S. Note that there is an additional complication resulting from the
> fact that functions are descriptors:
>
>>>> class C(dict):
> ... pass
> ...
>>>> C.__iter__
> <slot wrapper '__iter__' of 'dict' objects>
>>>> C().__iter__
> <method-wrapper object at 0x00E74A10>
>
> Even though the C instance is accessing the __iter__ function on the
> class, it gets back a different value because descriptors return
> different values depending on whether they are accessed from a
> class or
> an instance. I don't think you need to understand this to solve your
> problem though, so I won't go into any more details unless you
> think it
> would be helpful.
I found your explanation very helpful. After reading it I went back
and read
my Nutshell book and realized that the explanation was in there but I
didn't "get" it until now.
Although I did find an exception to the rule for attribute writes.
(See !Whoops below)
If you would care to elaborate on the how the lookup differs with
method descriptor
it would be most appreciated. Mostly because it seems that having
slots defined
changes the method lookup as opposed to the variable lookup and
apparently some of the type class
variables are special enough that they have their own rules.
This might help explain why it is that when I define __slots__, the
behavior when writing an attribute is different for
attributes that exist in the class versus attributes that exist in
__slots__ versus attributes that
do not exist at all. It is also different if the class attribute is a
method vesus a variable.
For example
>>> class C(dict):
... __slots__ = ['a','b']
...
>>> c = C()
>>> c.a
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: a
So slot defined but not assigned gets error
>>> c.a = 5
>>> c.a
5
OK here
>>> c.c
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object has no attribute 'c'
Surprise error gives no clue that slots is the reason for the error
>>> c.c = 4
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object has no attribute 'c'
ditto
Now the behavior is different for class variables and methods when
slots defined
versus when slots is not defined.
>>> c.__iter__ = 4
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object attribute '__iter__' is read-only
>>> super(C,c).__iter__ = 4
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: 'super' object has only read-only attributes (assign
to .__iter__)
>>>
>>> c.__class__ = C
>>> c.__class__
<class '__main__.C'>
it let me assign it! But not shadowed
>>> c.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object has no attribute '__dict__'
!Whoops now I am confused again. Didn't you say
> When "writing" an attribute (i.e. using the assignment statement),
> Python does not try to do any namespace searching. Thus if you use
> the
> instance in an assignment statement, then it is the instance's
> attributes that get modified, and if you use the class in an
> assignment
> statement, then it is the class's attributes that get modififed:
Then why wasn't __class__ added to c.__dict__ ? Looks like namespace
searching to me.
So to cross check if slots is not defined
>>> class C(dict):
... pass
...
>>> c = C()
>>> c.__iter__ = 1
>>> c.__dict__
{'__iter__': 1}
>>> c.__class__ = C
>>> c.__dict__
{'__iter__': 1}
>>>
try again a different way
class B(C):
... pass
...
>>> c.__class__ = B
>>> c.__dict__
{'__iter__': 4}
OK but maybe __class__ is magic, so I tried again
>>> class C(dict):
... a = 0
...
>>> c = C()
>>> c.a
0
>>> c.a = 4
>>> c.__dict__
{'a': 4}
OK __class__ is special
now with slots defined
>>> class C(dict):
... __slots__ = ['b']
... a = 0
...
>>> c = C()
>>> c.a
0
>>> c.a = 4
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object attribute 'a' is read-only
>>>
>>> C.a = 5
>>> c.a
5
>>>
So the rule is that when __slots__ is defined class variables become
read only.
What if the class variable is included in __slots__
>>> class C(dict):
... __slots__ = ['b']
... b = 1
...
>>> c = C()
>>> c.b
1
>>> c.b = 2
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object attribute 'b' is read-only
So even though b is in slots I still can't create an instance
variable by that name
and shadow the class variable.
It feels like the implementation of slots is half baked.
Finally
Since the "way" of python is that if an object does not have an
attribute
but you can assign it one then it creates one dynamically (in the
same 'way' that if a variable does not
exist is creates one upon assignment).
Because slots break this paradigm then at the very least the error
messages should point out that this object
is using slots "so beware".
For example I would prefer something like the following
c.a
AttributeError: Slot 'a' not yet assigned
c.c
AttributeError: No slot named 'c' on instance
c.c = 4
AttributeError: No slot named 'c' on instance
if change rule to not access class from instance when slots define
c.__iter__ = 4
AttributeError: No slot named '__iter__' on instance
or with current behavior
AttributeError: No slot name '__iter__' class 'C' object attribute
'__iter__' is read-only
super(C,c).__iter__ = 4
TypeError: 'super' object has only read-only attributes (assign
to .__iter__)
More information about the Python-list
mailing list