Thread Modes Distinguishing different types of class attributes

I’m learning Python OOP and I am struggling to understand the difference between defining the two different kinds of class attributes - - static attributes (declared above the dunder __init__ method) as compared to instance attributes (declared beneath and within the scope of the __init__ method).

When I search Google for ‘python difference dunder init method namespace’ (and similar) the results are split between guides and SO questions/answers explaining either the __init__.py files for Python packaging or the basics on how to instantiate classes in general. Another one here.

I can’t seem to locate a guide which directly addresses the comparison between the different types of the two attributes.

With the above search terms (and some variations), one of the recurring top results is the most useless document of all - - the entry on __init__ objects from the official Python website. I say ‘useless’ because the official doc is practically written in a foreign language (by programmers, for programmers).

What I am looking for is a casual, candid clarification in plain english with practical examples and a description of good potential use cases.

The closest I came to finding an answer to my original question is from SO. In that SO answer, here is a code snippet with its output after execution:

class MyClass(object):
    i = 123
    def __init__(self):
        self.i = 345
  
a = MyClass()
print(MyClass.i)
print(a.i)

Output:

123
345

Based on this code snippet, my understanding is that the declared first i attribute is universal and applies every time the class is called, whereas the second i also applies at run time, but is just a default container until the program at run time changes it to something else. Is this accurate? Could someone illustrate and better clarify the difference / distinction between the two different types of class attributes? Perhaps also sharing some meaningful use cases for both would be helpful too.

The larger question I seek an answer to is when (and in what context) would a programmer use one of the two possible attribute types instead of the other?

I came up with this question while watching Fred Baptiste’s Python course on OOP.

Hi enoren5,

>>> a = MyClass()
>>> b = MyClass()
>>> a.i
345
>>> del a.i
>>> a.i
123
>>> b.i
345
>>> del MyClass.i
>>> a.i
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute 'i'
>>> b.i
345

The class variable i is ‘universal’ (that is, common to all instances), while the instance variable i is object-specific and prior to the class variable.

The class variable is created when it is executed once, not “every time the class is called”, while the instance variable is created every time the object is instanciated.

Where things get interesting is when we go beyond what that SO example shows. Consider, for instance, this:

>>> class F:
...     i = 123
...     def __init__(self):
...         print(self.i)
...         self.i += 1
...         print(self.i)
... 
        
>>> f = F()
123
124
>>> g = F()
123
124
>>> F.i
123
>>> f.i
124
>>> g.i
124
>>> f.i = 200
>>> f.i
200
>>> g.i
124
>>> F.i
123
>>> F.i = 1
>>> h = F()
1
2
>>>

Every class variable is also an instance variable, inherently — when an instance of the class is created, its class variables are copied into the instance, automatically. There’s no need to “create” an instance variable i in a class that already has one; it’s already there. Always was. But if an instance modifies its own, local i, it will not affect the class variable, only the instance copy. Modifying the class variable, similarly, will only affect subsequent instantiations of the class.

Actually, what I wrote above isn’t quite correct — class variables aren’t copied into an instance. That implies more detachment than is really the case. It’s more accurate to say that an instance has access to all of the class’s variables. Initially, the names will all reference the class variables. So I can do this:

>>> # (Assume this session continues from the one above)
>>> F.color = "red"
>>> f.color
'red'
>>> F.color = "purple"
>>> f.color
'purple'
>>> g.color
'purple'
>>> f.color = "blue"
>>> F.color
'purple'
>>> f.color
'blue'
>>> g.color
'purple'

Assigning to a class variable changes the value in all instances that still reference it from the class. But in any instances where a variable with the same name has been assigned a value locally, it becomes detached and no longer references the class variable.

Every class variable is also an instance variable, inherently — when
an instance of the class is created, its class variables are copied
into the instance, automatically. There’s no need to “create” an
instance variable i in a class that already has one; it’s already
there.

This is incorrect.

A class attribute is bound to the class. That’s where its reference is
stored.

An instance attribute is bound to the instance.

You’re misinterpreting how attributes are accessed. What you access some
attribute via an instance, Python first looks in the instance. But if
there’s no such attribute, it then looks in the class.

In particular, the class attributes ARE NOT copied into the instance.
They’re fallback.

Look:

Python 3.9.10 (main, Jan 15 2022, 11:48:15)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class C:
...   i = 3
...
>>> c = C()

Now we have a class C and an instance of that class, c.

>>> C.i
3
>>> c.i
3

We can look up an i attribute on the class or the instance.

>>> C.__dict__
mappingproxy({'__module__': '__main__', 'i': 3, '__dict__': <attribute '__dict__' of 'C' objects>, '__weakref__': <attribute '__weakref__' of 'C' objects>, '__doc__': None})
>>> c.__dict__
{}

However, the reference to 3 is stored only on the class, not on
the instance. When we asked for c.i, the i attribute was not found
on c, so Python looked in the class and found it there.

>>> c.i = 4

This sets the attribute i on the instance. Now there are both a
class attribute and an instance attribute:

>>> c.i
4
>>> C.i
3

and you can see that now the instance has a reference to 4 as the
attribute named i:

>>> c.__dict__
{'i': 4}

The above example is a little contrived, but we do this a lot for class
level “constants” (no, not actually constants, but used as constants are
elsewhere):

class C1:
    DEFAULT_SIZE = 4
class C2:
    DEFAULT_SIZE = 6

If we have a function which could work on instances of either C1 or
C2 then we can do this:

def f(c, size=None):
    if size is None:
        size = c.DEFAULT_SIZE
    ... use the size for something ...

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

I knew I was screwing myself by editing my correction into the original comment, rather than posting a followup. But your explanation is more detailed anyway, so really I’m claiming successful application of Cunningham’s Law! :wink:

Attribute lookup on an instance first checks the class for a data descriptor – such as a property, member descriptor (e.g. an attribute defined by __slots__), or a getset descriptor (e.g. BaseException.__context__).