2

I am executing the following Python code :

class Pet :
    kind = 'dog'    # class variable shared by all instances
    tricks = []

    def __init__(self, name) :
        self.name = name # instance variable unique to each instance
    def changePet(self, newPet) :
        self.kind = newPet
    def addTricks(self, tricks) :
        self.tricks.append(tricks)

pet1 = Pet('mypet')
pet2 = Pet('yourpet')
print 'pet1 kind ::: ', pet1.kind;print 'pet2 kind ::: ', pet2.kind
print 'pet1 name ::: ', pet1.name;print 'pet2 name ::: ', pet2.name

Pet.kind = 'cat'
print 'changed Pet.kind to cat'
print 'pet1 kind ::: ', pet1.kind;print 'pet2 kind ::: ', pet2.kind

#changing pet#1 kind does not change pet#2 kind
pet1.changePet('parrot')
print 'changed pet1.kind to parrot'
print 'pet1 kind ::: ', pet1.kind;print 'pet2 kind ::: ', pet2.kind

pet1.addTricks('imitate')
pet2.addTricks('roll over')
print 'pet1 tricks ::: ', pet1.tricks;print 'pet2 tricks ::: ', pet2.tricks

print Pet.__dict__
print pet1.__dict__
print pet2.__dict__

The output is as per the explanation I found on internet. The output is as follows

pet1 kind :::  dog
pet2 kind :::  dog
pet1 name :::  mypet
pet2 name :::  yourpet
changed Pet.kind to cat
pet1 kind :::  cat
pet2 kind :::  cat
changed pet1.kind to parrot
pet1 kind :::  parrot
pet2 kind :::  cat
pet1 tricks :::  ['imitate', 'roll over']
pet2 tricks :::  ['imitate', 'roll over']
{'__module__': '__main__', 'tricks': ['imitate', 'roll over'], 'kind': 'cat', 'addTricks': <function addTricks at 0xb71fa6bc>, 'changePet': <function changePet at 0xb71fa33c>, '__doc__': None, '__init__': <function __init__ at 0xb71fa144>}
{'kind': 'parrot', 'name': 'mypet'}
{'name': 'yourpet'}

Now my query is that why is a mutable class object treated differently than a non-mutable class object

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
user3282758
  • 1,379
  • 11
  • 29
  • 3
    They are not treated differently by Python. **You** treated them differently, by assigning in one place, but using a list method in another. – Martijn Pieters May 09 '15 at 09:55
  • Try using `self.tricks = self.tricks + [tricks]` to see the difference. – Martijn Pieters May 09 '15 at 09:56
  • 1
    Relevant and related: [How do I avoid having Python class data shared among instances?](http://stackoverflow.com/q/1680528) – Martijn Pieters May 09 '15 at 09:58
  • Also, looks like you're a bit confused about terminology. There're no immutable variables involded in your example (except for strings, but it's essentially irrelevant). Proper terms would be _instance_ (`name`) and _shared_ (`kind` and `tricks`) attributes. Instance attributes are unique to invividual object (instance of the class), while shared are shared among *all* instances of the class. – J0HN May 09 '15 at 10:13
  • ok @ Martijn Pieters then how is `self.tricks += [tricks]` different from ` self.tricks = self.tricks + [tricks]` – user3282758 May 09 '15 at 10:58
  • Does this invoke list.append() @Martijn Pieter – user3282758 May 09 '15 at 11:05
  • 1
    `+=` is *augmented* assignment and gives the object the chance to update in-place; for a list object that uses `list.extend()`. – Martijn Pieters May 09 '15 at 11:19
  • @Martijn Pieter but `+=` also creates instance attribute `tricks` and now Pet, pet1 and pet2 all have two entry list in `tricks` – user3282758 May 09 '15 at 12:06
  • @user3282758: no, you extend the class attribute: https://gist.github.com/mjpieters/20bd97cbb8876f2c1e11; the same attribute is visible on all instances because there is no instance attribute masking it. – Martijn Pieters May 09 '15 at 12:17
  • @user3282758: `self.tricks += [tricks]` is the same thing as `self.tricks.extend([tricks])`, because list objects implement in-place augmented assignment support (one step, where `list.__iadd__([tricks])` returns the list object itself). `self.tricks = self.tricks + [tricks]` uses straight up concatenation (one step) then assignment (another step). – Martijn Pieters May 09 '15 at 12:27
  • @Martijn Pieter, I still did not understand completely. The `__dict__` of instance as well as class show same `tricks` in case of `+=` while in case of `self.tricks.append`, the new trick was added only to class `tricks` and the `__dict__` of instance did not show an entry for `tricks` at all – user3282758 May 09 '15 at 12:32
  • @user3282758: right, yes, the attribute is added to the instance, but it is the *same list*. I actually forgot `+=` does this; it does create an instance attribute but now the list object is shared with the class (no new list object is created). – Martijn Pieters May 09 '15 at 12:37
  • @Martijn Pieter I really appreciate your patience. When I am printing `pet1.tricks is Pet.tricks`, this returns `True` for both `append` as well as `+=`. So why is it that in case of `append` the `__dict__` of instance does not show entry for `tricks` but in case of `+=` the `__dict__` of instance does show an entry for `tricks` – user3282758 May 09 '15 at 12:45
  • @user3282758: because `list.append()` doesn't use assignment, `+=` does use assignment. Assignment on attributes on an instance creates an instance attribute if not yet there. – Martijn Pieters May 09 '15 at 12:48
  • @Martijn Piete, OK I think I understand it now – user3282758 May 09 '15 at 12:51

1 Answers1

3

I'm not sure that they treated differently. You are just performing different operations on them.

In the case of changePet, you are assigning the previously undefined self.kind to a value. Now when python tries to find pet1.kind, it finds it immediately. When looking up pet2.kind, it doesn't find it. It does, however, find find Pet.kind so it returns that.

In the case of addTricks, you are attempting to mutate self.tricks. Since that doesn't exist, you are instead given a reference to Pet.tricks. Therefore, when you call append() on self.tricks, you are effective calling Pet.tricks.append() and not self.tricks.append().

To clear things up, this:

def changePet(self, newPet) :
    self.kind = newPet
def addTricks(self, tricks) :
    self.tricks.append(tricks)

is effectively equivalent to this:

def changePet(self, newPet) :
    self.kind = newPet
def addTricks(self, tricks) :
    Pet.tricks.append(tricks)

To demonstrate that that python is not treating mutables differently than non-mutables, we need change your methods so that they perform similar operations:

def changePet(self, newPet) :
    self.kind = newPet

def addTricks(self, tricks):
    # assign self.tricks to a new value where we previously only mutated it!
    # note: list(self.tricks) returns a copy
    self.tricks = list(self.tricks)
    self.tricks.append(tricks)

Now when we run your code again, we get the following output:

pet1 kind :::  dog
pet2 kind :::  dog
pet1 name :::  mypet
pet2 name :::  yourpet
changed Pet.kind to cat
pet1 kind :::  cat
pet2 kind :::  cat
changed pet1.kind to parrot
pet1 kind :::  parrot
pet2 kind :::  cat
pet1 tricks :::  ['imitate']
pet2 tricks :::  ['roll over']
{'__module__': '__main__', 'tricks': [], 'kind': 'cat', 'addTricks': <function addTricks at 0x02A33C30>, 'changePet': <function changePet at 0x02A33BF0>, '__doc__': None, '__init__': <function __init__ at 0x02A33BB0>}
{'tricks': ['imitate'], 'kind': 'parrot', 'name': 'mypet'}
{'tricks': ['roll over'], 'name': 'yourpet'}
averes
  • 81
  • 2