Many beginners who just start out with object-oriented programming in Python are confused about the purpose of the keyword “self”. Let’s resolve this confusion once and for all!
The name self
is, by convention, the name of the first argument of a Python class method. The variable self
points to the concrete instance on which the method is called. It is used within the method to access attributes and other methods of the same instance. The caller of the method does not explicitly pass the instance into self but leaves it to the Python interpreter to pass it automatically. For example, given an object car
, Python automatically converts the method call car.drive()
to drive(self=car)
.
Letβs have a look at a practical example.
class SoccerPlayer: def __init__(self, health, salary): self.health = health self.salary = salary def foul(self, player): player.health = 0 self.salary = self.salary + 10**6 ronaldo = SoccerPlayer(100, 10**7) beckham = SoccerPlayer(100, 10**6) ronaldo.foul(beckham)
In the code snippet, you create a class SoccerPlayer
with two methods __init__
and foul
. The first arguments of the methods, named self
, are references to the instance that calls it.
In the constructor method __init__
, Python dynamically adds new instance attributes to the calling instance, denoted as health
and salary
.
Two instances of soccer players are created: ronaldo
and beckham
. You then call the foul()
method on the ronaldo
instance. When calling a method, you do not need to explicitly define the self
argument — Python does it for you. It takes the reference to the ronaldo
instance and passes it as the first argument, called self
, into the foul()
method.
A foul has a negative impact on the health of the victim. So as Ronaldo fouls Beckham, Beckham’s health declines and is set to zero. At the same time, the salary of Ronaldo goes up by 1,000,000 units—don’t ask me why.
Let’s have a look at a puzzle that checks your understanding so far. Can you solve it?
# PUZZLE: print(beckham.health) print(ronaldo.health) print(beckham.salary) print(ronaldo.salary) # What's the output of all four lines?
Exercise: What’s the output of this code snippet?
You can check if your guess is correct in the following interactive code shell. Just hit “Run” and see the result!
Explanation: The challenge of this puzzle is to differentiate the instance references self and player within the foul()
method.
So the result is the following:
- beckham.health reduces to 0
- ronaldo.health stays 100
- beckham.salary stays 1,000,000
- ronaldo.salary increases to 11,000,000
Now, let’s make this a bit harder, shall we?
class SoccerPlayer: def __init__(self, health, salary): self.health = health self.salary = salary def foul(self, player): player.health = 0 self.salary = self.salary + 10**6 ronaldo = SoccerPlayer(100, 10**7) beckham = SoccerPlayer(100, 10**6) SoccerPlayer.foul(beckham, ronaldo) print(beckham.health) print(ronaldo.health) print(beckham.salary) print(ronaldo.salary)
The puzzle seems similar to the above one but with an important difference: You call the foul()
method on the class rather than any particular instance. This has the benefit that we can explicitly define ALL arguments, including the self
argument. In this way, we can dynamically decide which instance fouls which other instance. In this puzzle, beckham
fouls ronaldo
. So Beckham get’s the salary bump and Ronaldo the health issues:
- beckham.health stays 100
- ronaldo.health reduces to 0
- beckham.salary doubles to 2,000,000
- ronaldo.salary stays at humble 10,000,000
Conclusion
In summary, the self
argument is similar to “this” in Java, or “@” in some other languages. Semantically, it refers to the instance on which the method modifies the data. Still, Python does not prevent a method to modify other instance’s data as well.
However, the self
argument is not a reserved keyword in Python. You can call it anything you want. Nevertheless, you shouldn’t do this as mentioned above. This clearly violates the expectations of all other Python coders out there.
There have been a few voices that requested that the self
argumentself
in his article. Although the article is written many years ago, the main arguments for (and against) the self argument still hold.
Here are two of his arguments from the article why self
should remain as is:
“There’s a pretty good argument to make that requiring explicit ‘self’ in the parameter list reinforces the theoretical equivalency between these two ways of calling a method, given that ‘foo’ is an instance of ‘C’:“
foo.meth(arg) == C.meth(foo, arg)
“Another argument for keeping explicit ‘self’ in the parameter list is the ability to dynamically modify a class by poking a function into it, which creates a corresponding method. For example, we could create a class that is completely equivalent to ‘C’ above as follows:“
# Define an empty class: class C: pass # Define a global function: def meth(myself, arg): myself.val = arg return myself.val # Poke the method into the class: C.meth = meth
There is a reason why Guido kept his place at the top for that many years!
I just want to note that the second argument is also what we have discussed above: Call the method on the class and define the
Where to Go From Here?
Forget what they tell you. You must know the Python basics by heart. If you cannot even write a small code snippet without googling keywords, the PEP standard, or basic functionality, you seriously harm your coding productivity.
To teach you the basics, I have created 5 FREE cheat sheets that teach you everything you need to know to get started. Fill in your email, download the cheat sheets, print them, and post them to your toilet wall! ?