DEV Community

loading...
Cover image for Python explicit self and turning function into method

Python explicit self and turning function into method

Kamal Mustafa
Python/Django Developer at GetOTP (otp.dev), fast and simple to integrate OTP API with ready made UI
ãƒģ3 min read

Python self is one of the source of confusion and questions when someone start learning Python, especially if they come from other languages such as Java, PHP, Ruby etc.

In all languages mentioned above, when you define a class, a reference to the class instance always created automatically. Take for example the following PHP code:-

<?php
class Person {
    public $name = "Kamal";

    public function info() {
        echo $this->name;
    }
}
Enter fullscreen mode Exit fullscreen mode

We can see above $this can be used to refer to the class instance. The same code in Python is like this:-

class Person:
    name = "Kamal"

    def info(self):
        print(self.name)
Enter fullscreen mode Exit fullscreen mode

So the code almost identical except for the difference in PHP the reference to class instance is called this while in Python it is self. And even self is arbitrary and simply pure convention. We can name it as this if we want.

The main difference between PHP and Python code above is that we have to specify the self parameter as a first parameter to the method definition. Omitting it will give us error when trying to call the instance method:-

>>> class Person:
...     name = "Kamal"
...     def info():
...         print(self.name)
...
>>> p = Person()
>>> p.info()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: info() takes 0 positional arguments but 1 was given
Enter fullscreen mode Exit fullscreen mode

One common question is why we have to explicitly define the self parameter? Why can't it be made implicit like in other language? One way to answer this is because the explicit self parameter allow us to specify different instance when calling the method. Take the following code:-

p = Person()
p.info()
Enter fullscreen mode Exit fullscreen mode

Above code actually just a syntatic sugar over calling a function in certain class and specifying the class instance as the first argument. The above code can also be written this way:-

p = Person()
Person.info(p)
Enter fullscreen mode Exit fullscreen mode

The following question could what the practical use case for this? I can't think anything specific on top of my head right now. Probably in situation when you want to call method on p dynamically.

However, when looking through the code of pq recently, I noticed something interesting with how they define the task() function. Some snippet of the function:-

def task(
    queue,
    schedule_at=None,
    expected_at=None,
    max_retries=0,
    retry_in='30s',
):
    def decorator(f):
        f._path = "%s.%s" % (f.__module__, f.__qualname__)
        f._max_retries = max_retries
        f._retry_in = retry_in

        queue.handler_registry[f._path] = f

        @wraps(f)
        def wrapper(*args, **kwargs):
Enter fullscreen mode Exit fullscreen mode

Just look at the function parameters and ignore the rest of the code. And this is how the docs show how to use it:-

from pq.tasks import PQ

pq = PQ(...)

queue = pq['default']

@queue.task(schedule_at='1h')
def eat(kind):
    print 'umm, %s apples taste good.' % kind

eat('Cox')

queue.work()
Enter fullscreen mode Exit fullscreen mode

So task() is a method of PG class? But we can see above that task() is defined as function? Turn out in line 94 the task function was added as attribute to class PQ. That essentially make it a method of that class. How this can work? Let see how the task() function will be used as function:-

pq = PQ(....)
queue = pq["default"]

@pq.task(queue, schedule_at='1h')
def eat(kind):
    print 'umm, %s apples taste good.' % kind
Enter fullscreen mode Exit fullscreen mode

Notice that when calling task() as function, we have to specify queue as its first argument, since that what it expecting as per the function definition. And when calling it as method, Python will automatically pass the PQ instance as the first argument, also satisfying the function requirement.

This pattern allow us to offer our library API both as function and method.

Discussion (0)