2

Why is letters shared between child instances below?

class Parent:
    letters = list()

    def add(self, letter):
        self.letters.append(letter)

class A(Parent):
    def add_a(self):
        self.add('a')

class B(Parent):
    def add_b(self):
        self.add('b')

a = A()
b = B()

a.add_a()
b.add_b()

print(a.letters)
# a.letters now contains a renegade b!

If I redefine Parent as such

class Parent:
    def __init__(self):
        self.letters = list()

    def add(self, letter):
        self.letters.append(letter)

a and b have separate lists of letters. This what I would've expected from the first code. What's the difference?

vindarmagnus
  • 326
  • 3
  • 10

1 Answers1

2

Extremely frequent mistake by beginners - did it for months myself. letters is assigned/bound to class Parents. Anyone looking at it and modifying it is doing so on that single copy of letters, which is shared (you can use print(f"{id(self.letters)=}") to see the variable's memory allocation identifier).

Inheritance has nothing to do with it, you'd have the same effect if you created 5 instances of any of your 3 classes and started adding letters. What happens is that letters is a mutable type and modifying it happily modifies the copy everyone shares even if self.letters makes it seem like a private copy.

In the 2nd case, you create a new letters for each new instance. This is most likely what you want here. By creating a new letters on each instance, you've avoided the unwanted sharing.

This behavior would be different if you were adding to a number or concatening to a string - those are immutable types and each instance would therefore be modifying its own local copy even if it started looking at the Parent in the first place. (basically, it reads the shared copy, but it assigns the result of any modification to a copy of that variable bound to the self namespace under the same name).

See also "Least Astonishment" and the Mutable Default Argument

Oh, and that trick about print(id(var))? Don't rely on it to do the same with integers under 255 - Python optimizes things under the cover by holding only 1 copy of all such variables. 2 in 5 different variables all have the same id and 6 in another 4 spots have share another unique id.

JL Peyret
  • 10,917
  • 2
  • 54
  • 73