3
  1. Are there any differences between a class variable and an instance variable with a default value?

(especially in terms of their behavior under "normal use", internally I suppose they most likely are implemented differently)

  1. In what context should I use which version?

Take these two classes as an example:

class A:
    d = 4

class A:
    def __init__(self, d=4):
        self.d = d

No matter what version you choose, when you run the code below, you'll get the same result:

a2 = A()

a = A()
print(a.d)   # 4
a.d = 2
print(a.d)   # 2

print(a2.d)  # 4

I came to think of this question after reading:

  1. class attribute behavior
Sebastian Nielsen
  • 3,835
  • 5
  • 27
  • 43

2 Answers2

5

Are there any differences between a class variable and an instance variable with a default value?

Well, yes obviously: a class attribute (not "variable") belongs to the class, an instance attribute belongs to the instance.

In what context should I use which version?

Use class attributes when you want the attribute to be shared by all instances of the class, and instance attributes when you want the attribute to be specific to this exact instance. In practice, you'll seldom have a need for class attributes.

Note that if you define the same attribute for both the class and the instance, the one on the instance will shadow the class's one.

nb: the above is a very very crude simplification, but else I'd need to explain the whole Python object model, and this deserves a full book

Take these two classes as an example (...) no matter what version you choose, when you run the code below, you'll get the same result

Yes, that's to be expected for this code snippet.

For a:

In the first case, when you first print a.d, a doesn't have an instance attribute d so you're getting the class attribute value. Then you create the instance attribute a.d by assigning to it, and from then on it shadows the class attribute.

In the second case, a.d initially has it's default value, then you rebind it to another value... quite ordinary stuff.

For a2:

In the first case, a2.a will always be 4 because you haven't shadowed it with an instance attribute, so it's getting the value from the class.

In the second case it will always be 4 because you didn't rebind the instance attribute so it's still the default value.

Now try the same thing with a list as attribute, and appending to the list instead of rebinding it:

class A:
    d = []

class B:
    def __init__(self):
        self.d = []


def test(cls):
    print("test {}".format(cls.__name__))
    a = cls()
    print(a.d)   
    a.d.append(2)
    print(a.d)   
    a2 = cls()
    print(a2.d)  

if __name__ == "__main__":
    test(A)
    test(B)

As a last note: you may have seen (or you may one day see) code using class attributes as default values for instances - or you may be tempted in doing so yourself (hence the mention of a 'default' value for the instance attribute) -, as in your first example. This is bad practice. It's confusing at best, and can lead to wrong behaviour if the attribute is of a mutable type.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Note that the question code has the default on the method *parameter*, not in the method *body*. – MisterMiyagi Jan 23 '20 at 16:12
  • @MisterMiyagi yes I know, but the OP doesn't use the initializer's argument, and using a list as default for an argument would open another can of worms (the infamous mutable default argument gotcha) – bruno desthuilliers Jan 23 '20 at 16:16
1

TLDR: The difference matters for visibility and special class attributes, such as descriptors. It also affects the class signature.


Similarities

When you define a class attribute, it is stored on the class. Similarly, when you define a default for a method, it is stored on the method and the method in turn is stored on the class. In the end, both class attribute and method default are stored on the class -- the latter merely adds a level of indirection.

class A:
    # store d on class
    d = 4

class B:
    # store init on class
    def __init__(self, d=4):  # store d on method
        self.d = d

Both values are accessible and writeable. They share the same properties with respect to mutability, e.g. if the value is a list.

>>> A.d
4
>>> B.__init__.__defaults__[0]
4
>>> A.d = 3
>>> B.__init__.__defaults__ = (3,)
>>> A.d
3
>>> B.__init__.__defaults__[0]
3

Differences

There is a difference for values that behave differently as class or instance attributes -- i.e. descriptors such as functions.

class AD:
    d = lambda x='Nothing': x

class BD:
    def __init__(self, d=lambda x='Nothing': x):
        self.d = d

Lookup will invoke or skip the descriptor protocol, leading to different behaviour:

>>> AD().d()  # class attribute
<__main__.AD at 0x10d4c7c10>
>>> BD().d()  # instance attribute
'Nothing'

Storing defaults on the class is inherently incompatible with descriptors for that attribute. For example, __slots__ and property require the default on __init__.

class BS:
    __slots__ = 'd',

    def __init__(self, d=4):
        self.d = 4

Application

The most important difference is that an __init__ default implies a parameter. A default stored on the class is not part of the class signature.

>>> B(d=42)
<__main__.B at 0x10d4dac90>
>>> A(d=42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: A() takes no arguments

As a result, always use __init__ defaults when the attribute is supposed to be customisable. In contrast, consider a class default when the attribute always starts with the same constant.

Note that if an attribute always starts with the same value but is not immutable, initialise it inside of __init__. If you need a default for a mutable attribute, use a placeholder and create the default in the method.

class C:
    def __init__(self, e=None):
        self.d = [1, 2, 3, 4]
        self.e = e if e is not None else []
MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119