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 []