0

I have written following code in python:

#!/usr/bin/python3

class A:
    l1 = [True for i in range(3)]
    l2 = []
    z = 3

    def __init__(self):
        self.l2 = [True for i in range(3)]

a = A()
b = A()
a.l1[0] = False
a.l2[0] = False
b.l1[1] = False
b.l2[1] = False
a.z = 1337
b.z = -1
print(a.l1)
print(a.l2)
print(b.l1)
print(b.l2)
print(a.z)
print(b.z)

Output:

[False, False, True]
[False, True, True]
[False, False, True]
[True, False, True]
1337
-1

My question is why l1 seems to be static member and z not?

Are all non-primitive objects initialized in class body made static?

I feel like it's one of those "magic wtf python" moments like with having list as default function parameter? (At least for me as person who mostly writes in C)

Thun
  • 37
  • 5
  • 1
    Attributes defined in the class body are class attributes. Attributes defined in methods (i.e., on `self`) are instance attributes. You can search this site for gazillions of questions about this distinction. – BrenBarn Apr 16 '17 at 18:23
  • If it was that in general then a.z would be equal to b.z – Thun Apr 16 '17 at 18:26
  • 3
    The distinction is that `l1` is a list, which is mutable and which you are modifying, whereas `z` is an integer, which is immutable and which you are reassigning. If you replaced the list, e.g. `a.l1 = ['new', 'list']`, you would see similar behaviour to `z`. – jonrsharpe Apr 16 '17 at 18:26
  • 1
    @Thun: No, because you're not doing the same operations to the objects. On `l1` and `l2` you're setting an item (mutating the existing value). On `z` you're assigning a new value. – BrenBarn Apr 16 '17 at 18:28
  • Thanks @jonrsharpe! I will read more about mutability then. Now it makes sense – Thun Apr 16 '17 at 18:30

1 Answers1

1

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!

GIZ
  • 4,409
  • 1
  • 24
  • 43
  • Thanks for elaborate answer! At first I thought about primitive/non-primitive types like in java. It's good to know about mutable vs immutable type. – Thun Apr 16 '17 at 20:44