That's not a declaration, that's an assignment ... to a variable inside the class, as opposed to a variable inside an instance.
Consider the following output:
>>> class K1(object):
... def __init__(self):
... self.attr = 'value'
...
>>> x = K1()
>>> x.__dict__
{'attr': 'value'}
>>> class K2(object):
... attr = 'value'
... def __init__(self):
... self.another = 'value2'
...
>>> y = K2()
>>> y.__dict__
{'another': 'value2'}
Here x
is an instance of class K1 and has an attribute named attr
, and y
is an instance of class K2 and has a different attribute named another
. But:
>>> y.attr
'value'
Where did that come from? It came from the class:
>>> y.__class__.__dict__
dict_proxy({'__module__': '__main__', 'attr': 'value',
'__dict__': <attribute '__dict__' of 'K2' objects>,
'__weakref__': <attribute '__weakref__' of 'K2' objects>,
'__doc__': None, '__init__': <function __init__ at 0x80185b9b0>})
That's kind of messy but you can see the attr
sitting in there. If you look at x.__class__.__dict__
there's no attr
:
>>> x.__class__.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'K1' objects>,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'K1' objects>,
'__doc__': None, '__init__': <function __init__ at 0x80185b938>})
When you get an attribute on an instance, like x.attr
or y.attr
, Python first looks for something attached to the instance itself. If nothing is found, though, it "looks upward" to see if something else defines that attribute. For classes with inheritance, that involves going through the "member resolution order" list. In this case there is no inheritance to worry about, but the next step is to look at the class itself. Here, in K2, there's an attribute in the class named attr
, so that's what y.attr produces.
You can change the class attribute to change what shows up in y.attr
:
>>> K2.attr = 'newvalue'
>>> y.attr
'newvalue'
And in fact, if you make another instance of K2(), it too will pick up the new value:
>>> z = K2()
>>> z.attr
'newvalue'
Note that changing x's attr
does not affect new instances of K1():
>>> w = K1()
>>> w.attr = 'private to w'
>>> w.attr
'private to w'
>>> x.attr
'value'
That's because w.attr
is really w.__dict__['attr']
, and x.attr
is really x.__dict__['attr']
. On the other hand, y.attr
and z.attr
are both really y.__class__.__dict__['attr']
and z.__class__.__dict__['attr']
, and since y.__class__
and z.__class__
are both K2
, changing K2.attr
changes both.
(I'm not sure the guy who wrote the page referenced in the original question realizes all this, though. Creating a class-level attribute and then creating an instance-level one with the same name is kind of pointless.)