简体   繁体   中英

Modifying a class attribute from instance, different behaviour depending on type?

If I create a class in Python and I give it a class attribute (this is taken directly from the docs, here ), as

class Dog:

    tricks = []            

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

I see that, as the docs suggest, when doing

d1 = Dog('d1') 
d2 = Dog('d2') 

d1.add_trick('trick') 
print d2.tricks

I get that the trick is added to d2 as well:

['trick']

This is because tricks is a class attribute rather than an instance attribute so gets shared across all instances (correct me if this is not orthodox!).

Now, suppose I do this instead

class Dog:

    a = 1

    def __init__(self, name):
        self.name = name

    def improve_a(self):
        self.a += 1

and I run

d1 = Dog('d1') 
d2 = Dog('d2') 

d1.improve_a() 
print d1.a, d2.a

this gives me 2 and 1 respectively, namely the count for the second instance has not changed. Why is this, why the behaviour difference?

The int class does not define the += operator ( __iadd__ method). That wouldn't make sense because it is immutable.

That's why += defaults to + and then = . reference

self.a += 1 becomes self.a = self.a + 1

Now the first time you call improve_a the following happens:

  1. read class attribute a and put it on the stack
  2. add 1 to the item on the stack
  3. create a new instance attribute a and assign it the value on the stack

That means the class attribute is not changed at all and you add a new instance attribute.

On every subsequent call of improve on the same object the instance attribute is incremented, because attribute lookup starts on the instance dict and will only go to the class dict if that attribute does not exist.

If you do the same with a mutable class which overloads the __iadd__ method you can get different behaviour:

class HasList:
    some_list = []

    def add_something(self, value):
        some_list += [value]

fst = HasList()
sec = HasList()

fst.add_something(1)
fst.add_something(2)
sec.add_something(3)

print(HasList.some_list, fst.some_list, sec.some_list)

You will see that all instances and the class itself still hold the same list. The print shows the same list [1, 2, 3] each time. You can also check that all three lists are identical: fst.some_list is sec.some_list and fst.some_list is HasList.some_list # -> True .

That is because list.__iadd__ just calls list.extend and returns itself inside the method (at least if it was written in python).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM