This behavior is a major pitfall for newbies in Python, this only requires little understanding to avoid it.
class A:
l1 = [True for i in range(3)]
l2 = []
z = 3
def __init__(self):
self.l2 = [True for i in range(3)]
l1
is a mutable object, that's an object that can be changed in-place. Take a look at this question:
Immutable vs Mutable types
And l1
is also a class-level attribute that spans all instances unless it's redefined lower in the class tree, in a superclass or an instance of the class:
class Sub(A):
l1 = ['new list']
or:
a = A()
a.l1 = ['new list']
This way you redefined an attribute higher in the class hierarchy and hence you'll receive the lowest-most attribute in your class tree. But this is not what you were asking about. Because z
is an immutable object--an object that you cannot change in-place like a list, when you code something like this:
a = A()
a.z = 20
You really define another z
in the instance's namespace and this shadows z
of class A
. For lists however, because they're mutable and because you can change them in-place, you'll get one reference of l1
for multiple instances, so for example:
class A:
l1 = [1, 2, 3]
>>> A().l1.append(4)
>>> A().l1 #Another instance created but same object l1
[1, 2, 3, 4]
What do we mean by in-place change? Since a list is a sequence that stores other objects, it technically has a reference for itself plus other references for its embedded objects. In-place assignments change a reference for an embedded object in lists, but not the reference of the list object itself.
Python Documentation:
Numeric objects are immutable; once created their value never changes. Python numbers are of course strongly related to mathematical numbers, but subject to the limitations of numerical representation in computers.
Study this code yourself and you'll notice the difference:
class A:
mutable = [True, False,] # shared by all objects, can be changed in-place
z = 22 # shared by all objects too, can't be changed in-place
def __init__(self):
self.l1 = [] # per-instance attribute
a = A()
b = A()
a.mutable[0] = False # Change A.mutable[0] index only, mutable shared by all objects
a.l1.append('S') # change l1 in-place, l1 not shared by all object
print(b.mutable) # print mutable, [False, False]
print(a.mutable) # print mutable, [False, False]
print(a.l1) # differing list objects, a.l1 is not b.l1
print(b.l1)
print(a.l1 is b.l1) # object identity check: is a.l1 same object as b.l1?
print(a.mutable is b.mutable) # is a.mutable same object as a.mutable?
# changing z
a.z = 24 # create a new int object called z in a
print(a.z) # print z of object a which is 24
print(A.z) # print A.Z which is 22
print(b.z) # print class's z, because b object doesn't have z itself
*Note: if you play around with lists or mutable objects generally speaking by changing their values in-place; in practice this will result in a very puzzling behavior for larger application. It'll be extremely tedious job to identify which line of code that changes the value of the mutable object in larger applications and this will intensify the debugging process. I always follow this advice and I think everyone should!