I have been playing around with a subclass of dict wrt a recipe for
setting dict items using attribute syntax.
The dict class has some read only attributes that generate an
exception if I try to assign a value to them.
I wanted to trap for this exception in a subclass using super but it
doesn't happen.
I have read Guido's tutorial on new style classes and Shalabh's
tuturial on new style attributes and methods, and thought
I understood what super was doing. But there is no discussion on read
only attributes and their associated magic.
It seems to me that there is some undocumented magic that does not
make sense to me.
for example
d = dict()
d.__iter__ returns
<method-wrapper object at 0x260a10>
If I try to assign a value to d.__iter__
d.__iter__ = False
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'dict' object attribute '__iter__' is read-only
If I use the setattr method, I get the exception also as expected
d.__setattr__('__iter__', False)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'dict' object attribute '__iter__' is read-only
but if I subclass and use super
class SD(dict):
pass
s = SD()
s.__iter__ returns
<method-wrapper object at 0x260a10>
so the object s has this attribute
hasattr(s,'__iter__') also returns
True
but s.__dict__ is empty at this stage so s has inherited this
attribute as a method
so far so good.
If I assign a value
s.__iter__ = False
it lets me but adds the attribute to s.__dict__ thereby shadowing the
method, no surprises yet
s.__dict__ returns
{'__iter__': False}
but I want to know if the attribute is a read only attribute of the
super class so that I don't shadow it
in the subclass. So I start over and try
s = SD()
super(SD,s).__setattr__('__iter__', True)
Expecting to get the ReadOnly exception but I don't get the exception.
Instead the attribute is added to s.__dict__.
s.__dict__ returns
{'__iter__': True}
Shouldn't the super __setattr__ call be using the same code as the
direct call to dict's __setattr__
and therefore produce an exception? 6 5860
Samuel M. Smith wrote: The dict class has some read only attributes that generate an exception if I try to assign a value to them. I wanted to trap for this exception in a subclass using super but it doesn't happen.
class SD(dict): pass
[snip] s = SD() super(SD,s).__setattr__('__iter__', True)
Expecting to get the ReadOnly exception but I don't get the exception.
Note that __iter__ is on the dict *type* not dict instances. Try this:
py> class SD(dict):
.... pass
....
py> super(SD, SD).__init__ = False
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
AttributeError: 'super' object attribute '__init__' is read-only
You can always shadow class-level attributes in the instance dict.
(That's what you were doing.) If you want to (try to) replace an
attribute in the class dict, you need to use the class object, not an
instance object.
HTH,
STeVe
On 06 Dec, 2005, at 20:53, Steven Bethard wrote: Samuel M. Smith wrote: The dict class has some read only attributes that generate an exception if I try to assign a value to them. I wanted to trap for this exception in a subclass using super but it doesn't happen.
class SD(dict): pass [snip] s = SD() super(SD,s).__setattr__('__iter__', True)
Expecting to get the ReadOnly exception but I don't get the exception.
Note that __iter__ is on the dict *type* not dict instances. Try this:
py> class SD(dict): ... pass ... py> super(SD, SD).__init__ = False Traceback (most recent call last): File "<interactive input>", line 1, in ? AttributeError: 'super' object attribute '__init__' is read-only
You can always shadow class-level attributes in the instance dict. (That's what you were doing.) If you want to (try to) replace an attribute in the class dict, you need to use the class object, not an instance object.
I guess that's where my understanding breaks down. I thought the only
way to access class attributes was by
calling the class directly as your example indicates but __iter__ is
a class attribute that I can access from the instance
at least to read it. So what determines which class attributes get
copied to the instance and which ones don't?
Samuel M. Smith wrote: On 06 Dec, 2005, at 20:53, Steven Bethard wrote: You can always shadow class-level attributes in the instance dict. (That's what you were doing.) If you want to (try to) replace an attribute in the class dict, you need to use the class object, not an instance object.
I guess that's where my understanding breaks down. I thought the only way to access class attributes was by calling the class directly as your example indicates but __iter__ is a class attribute that I can access from the instance at least to read it. So what determines which class attributes get copied to the instance and which ones don't?
When "reading" an attribute, Python looks through the namespaces in the
order (instance, type). So if the attribute exists in the instance, the
instance-level value is returned. If the attribute does not exist in
the instance, but does exist in the class, then the class-level value is
returned: class C(object):
.... x = 1
.... inst = C() inst.x
1 C.x
1 class C(object):
.... x = 1
.... def __init__(self):
.... self.x = 2
.... inst = C() inst.x
2 C.x
1
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:
class C(object):
.... pass
.... inst = C() inst.x = 1 C.x
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
AttributeError: type object 'C' has no attribute 'x' inst.x
1 class C(object):
.... pass
.... inst = C() C.x = 1 inst.x
1 C.x
1
HTH,
STeVe
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.
> 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__)
Samuel M. Smith wrote: If you would care to elaborate on the how the lookup differs with method descriptor it would be most appreciated.
For the more authoritative guide, see: http://users.rcn.com/python/download/Descriptor.htm
The basic idea is that a descriptor is an object that sits at the class
level, and redefines how some attribute accesses work. Consider a
simple example: class D(object):
.... def __get__(self, obj, objtype=None):
.... if obj is None:
.... return 'called from class %r' % objtype
.... else:
.... return 'called from instance %r' % obj
.... class C(object):
.... d = D()
.... C.d
"called from class <class '__main__.C'>" C().d
'called from instance <__main__.C object at 0x00E73A30>'
As you can see, instances of the D class, when used as class attributes,
can tell whether they're being called by the class or the instance.
This means that descriptors with a __get__ method defined can do just
about anything on an attribute access.
Note that all functions in Python are descriptors, and they use the
__get__ method to return either an unbound method or a bound method,
depending on whether they were called from the type or the instance:
def f(x):
.... return x*2
.... class C(object):
.... func = f
.... f
<function f at 0x00E69C30> C.func
<unbound method C.f> C().func
<bound method C.f of <__main__.C object at 0x00E73B50>>
This might help explain why it is that when I define __slots__, the behavior when writing an attribute is different
Yes. Defining __slots__ basically tells the class to create descriptors
for each name in the list. So, for example:
>>> class C(dict): ... __slots__ = ['a','b'] ...
Creates two descriptors that are attributes of class C: one named "a"
and one named "b".
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
Here, Python is trying to set the "__iter__" attribute of the object.
Since you defined __slots__, it tells you that it can't. So it never
even looks at the type. >>> 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__)
In this case, you explicitly request the superclass, so you get the same
error as before because you bypass the __slots__, which are defined for
instances of C, not for instances of the superclass, dict.
Then why wasn't __class__ added to c.__dict__ ? Looks like namespace searching to me.
No, as you conclude later, __class__ is special, so you can still assign
to __class__ even when __slots__ is defined because it's not considered
a normal attribute. But note that __class__ is an *instance* attribute,
not a class attribute, so "c.__class__ = C" changes the class of that
single instance, and makes no change to the type: class C(object):
.... pass
.... class D(C):
.... pass
.... c1 = C() c2 = C() C, c1, c2
(<class '__main__.C'>, <__main__.C object at 0x00E73A30>, <__main__.C
object at 0x00E73210>) c1.__class__ = D C, c1, c2
(<class '__main__.C'>, <__main__.D object at 0x00E73A30>, <__main__.C
object at 0x00E73210>)
So no, even with __class__, you're only assigning to the instance, and
so Python's not searching any additional namespaces.
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.
That's not quite right. As you show above, class variables are still
modifiable from the class object. But yes, defining __slots__ means
that, from an instance, you can only modify the attributes defined in
__slots__.
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.
Yes, this behavior is documented: http://docs.python.org/ref/slots.html
"""
__slots__ are implemented at the class level by creating descriptors
(3.3.2) for each variable name. As a result, class attributes cannot be
used to set default values for instance variables defined by __slots__;
otherwise, the class attribute would overwrite the descriptor assignment.
"""
The documentation isn't great, I'll agree, but the result is basically
that if you combine __slots__ with class attributes of the same name,
you're asking for trouble because your 'b' class attribute and your 'b'
slot both reside at the class level. IMO, the manual should probably
explicitly say that the behavior of such a class is undefined.
It feels like the implementation of slots is half baked.
I wouldn't go that far. Once you understand that __slots__ reside as
descriptors at the class level, you can see why most of the problems
arise. That said, I've never had the need to use __slots__ in any real
world code.
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__)
These seem like pretty reasonable requests. I would suggest adding them
as a feature request: http://sourceforge.net/tracker/?grou...70&atid=355470
The one thing I don't know is how hard it is to produce these messages
-- I don't know the C-level code for __slots__ at all. Hopefully
someone else will!
STeVe
> Then why wasn't __class__ added to c.__dict__ ? Looks like namespace searching to me.
No, as you conclude later, __class__ is special, so you can still assign to __class__ even when __slots__ is defined because it's not considered a normal attribute. But note that __class__ is an *instance* attribute, not a class attribute, so "c.__class__ = C" changes the class of that single instance, and makes no change to the type:
So no, even with __class__, you're only assigning to the instance, and so Python's not searching any additional namespaces.
Thank you so much, very helpful comments. I hope it helps others as
well.
At the very least I now understand a little better about some of the
special
rules for special attributes. This thread has been closed and replies have been disabled. Please start a new discussion. Similar topics
by: Dan Perl |
last post by:
There is something with initializing mutable class attributes that I am
struggling with. I'll use an example to explain:
class Father:
attr1=None # this is OK
attr2= # this is wrong...
|
by: Simon Wittber |
last post by:
For the first time, I have been bitten by Python. The below code
produces the results:
False
True
when I initially expected the results:
False
False
It took me a while to work out that...
|
by: Derek Basch |
last post by:
Hello Everyone,
Given:
class A:
def __init__(self):
super(A, self).__init__()
self.dog = "fluffy"
def changeDog(self):
self.dog = "spike"
|
by: John J |
last post by:
Thankyou to those people in this newsgroup that have answered my questions
and helped me to learn whilst developing my yacht race classes.
I have now completed the three classes (Yacht, Race and...
|
by: Dave Hansen |
last post by:
OK, first, I don't often have the time to read this group, so
apologies if this is a FAQ, though I couldn't find anything at
python.org.
Second, this isn't my code. I wouldn't do this. But a...
|
by: Gene |
last post by:
I'm not sure I understand this behavior (at least in the context of C#).
I understand the use of "ref" and "out" keywords as they apply to method
arguments, and then something this morning...
|
by: G Dean Blake |
last post by:
I am writing a control that prints any datagrid. My control gets passed a
datagrid object and examines it, formats, and prints it. I am, however,
encountering what I consider unexpected behavior...
|
by: Jeff Louie |
last post by:
In C# (and C++/cli) the destructor will be called even if an exception
is thrown in the constructor. IMHO, this is unexpected behavior that can
lead to an invalid system state. So beware!
...
|
by: John M. Gabriele |
last post by:
The following short program fails:
----------------------- code ------------------------
#!/usr/bin/python
class Parent( object ):
def __init__( self ):
self.x = 9
print "Inside...
|
by: lllomh |
last post by:
Define the method first
this.state = {
buttonBackgroundColor: 'green',
isBlinking: false, // A new status is added to identify whether the button is blinking or not
}
autoStart=()=>{
|
by: DJRhino |
last post by:
Was curious if anyone else was having this same issue or not....
I was just Up/Down graded to windows 11 and now my access combo boxes are not acting right. With win 10 I could start typing...
|
by: isladogs |
last post by:
The next Access Europe meeting will be on Wednesday 4 Oct 2023 starting at 18:00 UK time (6PM UTC+1) and finishing at about 19:15 (7.15PM)
The start time is equivalent to 19:00 (7PM) in Central...
|
by: Aliciasmith |
last post by:
In an age dominated by smartphones, having a mobile app for your business is no longer an option; it's a necessity. Whether you're a startup or an established enterprise, finding the right mobile app...
|
by: tracyyun |
last post by:
Hello everyone,
I have a question and would like some advice on network connectivity. I have one computer connected to my router via WiFi, but I have two other computers that I want to be able to...
|
by: giovanniandrean |
last post by:
The energy model is structured as follows and uses excel sheets to give input data:
1-Utility.py contains all the functions needed to calculate the variables and other minor things (mentions...
|
by: NeoPa |
last post by:
Hello everyone.
I find myself stuck trying to find the VBA way to get Access to create a PDF of the currently-selected (and open) object (Form or Report).
I know it can be done by selecting :...
|
by: isladogs |
last post by:
The next Access Europe meeting will be on Wednesday 1 Nov 2023 starting at 18:00 UK time (6PM UTC) and finishing at about 19:15 (7.15PM)
Please note that the UK and Europe revert to winter time on...
|
by: nia12 |
last post by:
Hi there,
I am very new to Access so apologies if any of this is obvious/not clear.
I am creating a data collection tool for health care employees to complete. It consists of a number of...
| |