Self in Python Object Orientation

Discover Self in Python [+ Video Guide]

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 first argument of any instance method in Python is reserved for a reference to the calling instance. This argument is by convention called “self”. However, NEVER violate this convention and call it anything else because the people reading your code will start hating you.

Let’s have a look at an example code puzzle. Can you solve it?

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)

print(beckham.health)
print(ronaldo.health)

print(beckham.salary)
print(ronaldo.salary)

In the code snippet, a class SoccerPlayer is created with two methods __init__ and foul. The first arguments of the methods are references to the instance that calls it.

Hence, 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. Then, we 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).

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: We 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

Conclusions

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 argument should be a reserved keyword in future Python versions. But the creator of Python Guido van Rossum has explicitly defended the current use of self 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 self argument explicitly rather than letting it Python do for us.

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! 🤓

Leave a Comment