Reputation: 1128
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
__init__
(e.g.
self.tricks = tricks). I wouldn't expect it to be possible to set
self.tricks since it wasn't previously defined.a.tricks = ['foo']
seem to behave as intended compared a.add_trick('roll over')
? add_trick
before setting a.trick result in a different outcome?*Here's what I've read so far, are there better explanations?
Upvotes: 0
Views: 204
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