Peter
Peter

Reputation: 1128

Classes and self in python: instance and class variables

I'm a novice Python programmer utterly confused by OOP and the need for self in classes. I've read the many SO posts, blogs* etc that attempt to demystify it, but could use some help on this particular scenario.

I am basing the below scenarios off the Python2 tutorial on Class and Instance Variables. I'll lead with my examples, followed by explicit questions beneath.

First, I'll directly set tricks, and then use the add_trick method:

class Dog:

    tricks = []      

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

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

    def print_self_tricks(self):
        print(self.tricks)

a = Dog('spencer')
b = Dog('rex')

a.tricks = ['foo'] # How is this even possible? Where did I define self.tricks? 

a.print_self_tricks()
['foo']    # Ok, the below results make sense
b.print_self_tricks()
[]
a.tricks
['foo']
b.tricks
[]

a.add_trick('jump')

a.print_self_tricks()
['foo', 'jump']
b.print_self_tricks()
[]          # Great, as expected
a.tricks
['foo', 'jump']
b.tricks
[]          # Great, as expected

Now, the exact same setup, but executed in a different order: add_trick before direct setting of trick.

class Dog:

    tricks = []      

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

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

    def print_self_tricks(self):
        print(self.tricks)

a = Dog('spencer')
b = Dog('rex')

a.add_trick('jump')

a.print_self_tricks()
['jump']
b.print_self_tricks() 
['jump']    # Welp, that's not expected. 
a.tricks
['jump']
b.tricks
['jump']    # Welp, that's not expected. 

a.tricks = ['foo']

a.print_self_tricks()
['foo']
b.print_self_tricks() 
['jump']    # Great, but inconsistent with the behavior above resulting from a.add_trick
a.tricks
['foo']
b.tricks
['jump']    # Great, but inconsistent with the behavior above resulting from a.add_trick

*Here's what I've read so far, are there better explanations?

Upvotes: 0

Views: 204

Answers (1)

Aasmund Eldhuset
Aasmund Eldhuset

Reputation: 37940

class Dog:
    tricks = []

This defines tricks as a property on the class, not on any instance. A class property is in a sense "shared" between all instances of the class. It should ideally be accessed via the class: Dog.tricks, but Python also allows access via an instance, e.g. a.tricks or self.tricks. The rule is that if x is an instance, x.y will refer to the attribute y of the instance x if it exists, otherwise the attribute y of x's class.

a.tricks = ['foo']

Python is a dynamically-typed language which does not require advance declarations of classes, attributes, or variables. This statement creates an attribute on the Dog instance that a refers to, and makes it refer to a list with one string. Before this, a.tricks would resolve to the still-empty list that the class attribute refers to, but after this, a.tricks will resolve to ['foo']. tricks has not been set on b, so b.tricks still resolves to the same thing as Dog.tricks.

So the behavior you're seeing is because until you explicitly set a.tricks to something, a and b are sharing the class attribute. You probably didn't intend this - try the below, which will give each instance its own tricks:

class Dog:
    def __init__(self, name):
        self.name = name
        self.tricks = []

Upvotes: 4

Related Questions