Class variables

Hi,

I am confused about class variable and instance variable in below example:

class Classy:
varia = 2
def method(self):
print(self.varia, self.var)

obj = Classy()
obj.var = 3
obj.method()

Don’t we access class variables like Example.varia and access object variables as self.varia?

By Alex Lu via Discussions on Python.org at 14Aug2022 23:28:

I am confused about class variable and instance variable in below
example:

class Classy:
varia = 2
def method(self):
print(self.varia, self.var)

obj = Classy()
obj.var = 3
obj.method()

Don’t we access class variables like Example.varia and access object
variables as self.varia?

Well… yes and no.

Yes, to be clear, you would usually refer to a class attribute via the
class. But in an instance method (a normal" method), you’ve got self,
not the class. You could write:

type(self).varia

but you can, more conveniently, write:

self.varia

provided that self does not directly have an attribute .varia.

One nice thing about a class variable (“attribute”) is that you can
access via instances - if the instance does not has that attribute, it
is sought on the class. And the handy thing about that is inheritance:

class ClassA:

    DEFAULT_N = 1

    def method(self, n=None):
        if n is None:
            n = self.DEFAULT_N
        print(n)

class ClassB(ClassA):

    DEFAULT_N = 2

b = ClassB()
b.method()

should print 2. because self.DEFAULT_N looks in the class hierarchy.
For a ClassB it find it there, but would have fallen back to ClassA
if it were not overridden.

And (dubious but not insane) one could even provide, by hand, a
.DEFAULT_N on a particular instance, too, for a special circumstance.

As a more general view, an expression like this:

self.method(3)

does 2 things:

  • look up the attribute named method on self
  • calls what it found with (3) as the arguments

From that point of view, how would method calls work if they did not
fall back from the instance to the class? because self.method is a
valid expression in its own right.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

In Python, the preferred terminology is attribute, not “variable”.

In Python, classes are themselves objects which we can treat as values. So if an int variable is a variable that stores an int, and string variable is a variable intended to store strings, and a list variable is a variable that stores lists, a class variable would be a variable that stores classes.

Attribute lookup from a class object can only see attributes defined on the class itself, or one of its superclasses. To keep things simple, I will ignore superclasses, and just have a single class:

class MyClass:
    attr = 'from the class'

instance = MyClass()

So looking up MyClass.attr will return ‘from the class’.

Attribute lookup from an instance can see attributes defined on the class or on the instance. That is necessary so that methods can be found. In Python, methods are just plain old attributes that happen to also be callable like a function:

value = "abcd".upper  # Look up, but don't call, the "upper" attribute on a string.
print(value)

Attribute lookup from an instance starts the search at the instance, then if not found, it goes on to the class, and the superclasses. So looking up instance.attr will also return ‘from the class’.

But when we assign to the instance attribute:

instance.attr = 'from the instance'

now the class attribute is untouched, but access to it is blocked by the newly created instance attribute:

print(instance.attr)  # will print 'from the instance'
print(MyClass.attr)   # will print 'from the class'

Remember that Python is dynamic, and the interpreter doesn’t know what attributes exist at compile time, so attribute lookup has to occur at run time. Attributes are (usually) stored as objects in a special dictionary, called __dict__. Each class, and most (but not all) instances, have that dictionary.

The simplified attribute lookup process goes something like this, halting on the first successful lookup:

if the object is an instance:
    check the instance `__dict__`

check the class `__dict__`
for each superclass in the class' inheritance tree:
    check the superclass `__dict__`

if still not found:
    raise AttributeError

The real search process is much more complex, with memory-efficient slots, dynamic attributes created on the fly with the __getattr__ method, the descriptor protocol, metaclasses and more. But you don’t need to care about any of that right now: it is all carefully designed so that the simple cases remain simple.

Methods say “What are we, chopped liver?”

Methods are class attributes, and we almost always refer to them via the instance.

If we include methods, and we should since in Python there is no difference in looking up a method versus looking up other attributes, I would say that we usually refer to class attributes via the instance!

By Steven D’Aprano via Discussions on Python.org at 15Aug2022 01:23:

Methods say “What are we, chopped liver?”

Methods are class attributes, and we almost always refer to them via the instance.

Yes, as alluded to at the bottom of my post. Yours is clearer, if less
tied to the OP’s example.

If we include methods, and we should since in Python there is no difference in looking up a method versus looking up other attributes, I would say that we usually refer to class attributes via the instance!

Bah! Only based on actually counting real world use.

The OP wasn’t really thinking of method lookup in these terms, and it is
a little different semanticly:

  • ClassName.method → unbound method
  • instance.method → bound method
    and their calling signatures are thus different.

But yes. Not big on chopped liver myself.

Cheers,
Cameron Simpson cs@cskk.id.au

Attribute lookup directly on a class also checks the type of the class (i.e. the metaclass) if the class doesn’t override the attribute.

Function objects are non-data descriptors (i.e. they only define __get__()), and non-descriptors and non-data descriptors use somewhat different rules compared to data descriptors such as property objects (i.e. those that also define __set__() and/or __delete__()). An instance attribute can override a non-descriptor or non-data descriptor attribute that’s set on the type of the instance. An instance attribute cannot override a data descriptor attribute from its type. Here’s an example demonstrating both this and the metaclass behavior:

class Meta(type):
    @property
    def spam(self):
        return 84

class C(metaclass=Meta):
    @property
    def spam(self):
        return 42
>>> C.spam
84
>>> x = C()
>>> x.spam
42

>>> # instance data can't override a data descriptor set on the class
>>> vars(x)['spam'] = 0
>>> x.spam
42