Python OOP - Method vs Function and the Mystery of ‘self’

I just realized how much I didn't know about Python Object-Oriented Programming. I thought I knew the basics, but a few days ago, while going through a Python course, I found out I was wrong. Before I forget what I’ve learned, I wanted to write this blog post and share it with you.

In this blog post, we’ll cover the difference between functions and methods, and what exactly ‘self’ means in Python. So, let’s get to it.

Functions vs Method

class MyClass:
    def say_hello():
        print('Hello')

In this snippet, we’ve defined a Class called MyClass with a function named say_hello. But here’s a question for you - what do you call say_hello? Is it a function or a method?

It’s a common misconception to think that simply defining a function inside a Class automatically makes it a method. However, the distinction lies in how the function is accessed.

  1. Function - When you define a function inside a class, it’s just a regular function until it’s accessed through an instance of the class.
  2. Method - When you access that function via an instance of the class (e.g., `obj.say_hello'), it becomes a method. This is because it’s implicitly passed the instance (self), allowing it to operate on the instance’s data.

In the screenshot, you can see this in action. When you access say_hello directly from the class (MyClass.say_hello), it remains a function. But when accessed from an instance (my_obj.say_hello), it becomes a method.

Bound Method

If you look at the screenshot, you might notice it says 'bound' method when you create an instance of MyClass and call say_hello using that instance. At this point, say_hello becomes a bound method. This means the function say_hello is now tied, or “bound,” to the instance obj.

In simple terms, when you access say_hello from the class itself, it’s just a function. But when you access it through an instance of the class, it becomes a method that knows which specific instance it’s working with.

Using Parentheses to Call a Method

You might have also noticed that when I referenced the function earlier, I didn’t use parentheses. That’s because, without the parentheses, I’m not actually calling the function, instead, I’m just getting a reference to it.

This means that say_hello is recognized as a bound method, but it doesn’t execute or print anything until I add the parentheses. When you do add the parentheses, like obj.say_hello(), you’re telling Python to run the method, which then executes the code inside and prints Hello.

Mystery of 'self'

Now, let's actually call the function from both the Class itself and an instance of the Class. You can see that calling the function directly from the class works fine, but when we try to call it from the object, it fails with an error.

The reason for this is that when you call the function from an instance of the Class, Python automatically tries to pass the instance as the first argument to the function. This is where the self parameter comes into play. In our example, the function doesn’t expect any arguments, but Python is trying to pass the instance itself as an argument, causing the error.

To fix this, we need to include self as the first parameter in our function definition.

Calling the Function from the Class

In this next example, when we try to call say_hello from the Class by passing an object explicitly as an argument, like MyClass.say_hello(obj), it works fine. This is because the object obj is manually passed as the first argument, fulfilling the function's expectation of receiving a self parameter.

However, when we later try to call MyClass.say_hello() without any arguments, we encounter a TypeError. The error message tells us that say_hello is missing one required positional argument: self.

This happens because, in Python, when you call a method on an instance (like obj.say_hello()), Python automatically passes the instance (obj) as the first argument to the method. But when you call the method directly from the Class without an instance or without passing any arguments, there’s no instance to pass, so Python raises an error since it’s expecting that first self argument.

Do I have to Use the Word 'self'?

You might be wondering, do you always have to use the word self in your method definitions? The answer is no. Self is not a keyword in Python, but it is a strong convention. You could technically use any word in place of self, but it’s highly recommended to stick with self because it makes your code more readable and follows the standard Python practices. Using self ensures that anyone reading your code immediately understands that the method is referring to the instance itself.