Search code examples
pythonpython-3.xprogramming-languages

Why the Python scope seems to behave differently for list variable and string variable?


Say that I have the following Python code:

import sys

class DogStr:
    tricks = ''

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

class DogList:
    tricks = []

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

# Dealing with DogStr
d = DogStr()
e = DogStr()
d.add_trick('trick d')
e.add_trick('trick e')
print(d.tricks)
print(e.tricks)

# Dealing with DogList
d = DogList()
e = DogList()
d.add_trick('trick d')
e.add_trick('trick e')
print(d.tricks)
print(e.tricks)

Running this code with Python 3.6.5, I get the following output:

trick d
trick e
['trick d', 'trick e']
['trick d', 'trick e']

The difference between DogStr and DogList is that I treat tricks as a string on former and as a list on the latter.

When dealing with DogStr, tricks is behaving as an instance variable. BUT with DogList tricks is behaving as a class variable.

I was expecting to see the same behaviour on both calls, i.e.: if the two last lines of the output are identical, so should be the first two.

So I wonder. What is the explanation for that?


Solution

  • The difference is not int the type of the object, but in what your code does to it.

    There is a big difference between these two:

    self.tricks = trick
    

    and:

    self.tricks.append(trick)
    

    The first one self.tricks = trick assigns a value to attribute tricks of self.

    The second one self.tricks.append(trick) retrieves self.tricks and calls a method on it (which here modifies its values).


    The problem, in your case, is that there is no tricks defined on self instance, so self.tricks.append gets the tricks attribute of the class and modifies it, but self.tricks = ... creates a new attribute on self instead.

    The fact that one of them is a string and the other is a list is not really relevant. It would be the same if both were lists. Note that they could not both be strings because strings are immutable and thus have no append method

    How to fix it?

    This is wrong:

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

    If tricks is a class attribute, add_trick should be a class method:

    @classmethod
    def add_trick(cls, trick):
        cls.tricks = trick
    

    If there are reasons for add_trick to be an instance method, then simply do this:

    def add_trick(self, trick):
        DogStr.tricks = trick