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