J.K.
J.K.

Reputation: 1616

Is `self` actually mandatory for class methods in Python?

I saw a code snippet in Python 3.6.5 that can be replicated with this simplified example below and I do not understand if this is something concerning or not. I am surprised it works honestly...

class Foo:

    def bar(numb):
        return numb

    A1 = bar(1)


print(Foo)
print(Foo.A1)
print(Foo.bar(17))

In all python guides that I have seen, self appears as the first argument for all the purposes we know and love. When it is not, the methods are decorated with a static decorator and all is well. This case works as it is, however. If I were to use the static decorator on bar, I get a TypeError when setting A1:

Traceback (most recent call last):
  File "/home/user/dir/understanding_classes.py", line 1, in <module>
    class Foo:
  File "/home/user/dir/understanding_classes.py", line 7, in Foo
    A1 = bar(1)
TypeError: 'staticmethod' object is not callable

Is this something that is OK keeping in the code or is this a potential problem? I hope the question is not too broad, but how and why does this work?

Upvotes: 3

Views: 131

Answers (1)

Ryan Haining
Ryan Haining

Reputation: 36892

The first parameter of the method will be set to the receiver. We call it self by convention, but self isn't a keyword; any valid parameter name would work just as well.

There's two different ways to invoke a method that are relevant here. Let's say we have a simple Person class with a name and a say_hi method

class Person:
    def __init__(self, name):
        self.name = name

    def say_hi(self):
        print(f'Hi my name is {self.name}')


p = Person('J.K.')

If we call the method on p, we'll get a call to say_hi with self=p

p.say_hi() # self=p, prints 'Hi my name is J.K.'

What you're doing in your example is calling the method via the class, and passing that first argument explicitly. The equivalent call here would be

Person.say_hi(p) # explicit self=p, also prints 'Hi my name is J.K.'

In your example you're using a non-static method then calling it through the class, then explicitly passing the first parameter. It happens to work but it doesn't make a lot of sense because you should be able to invoke a non-static method by saying

f = Foo()
f.bar() # numb = f, works, but numb isn't a number it's a Foo

If you want to put a function inside of a class that doesn't have a receiver, that's when you want to use @staticmethod (or, @classmethod more often)

class Person:
    def __init__(self, name):
        self.name = name

    def say_hi(self):
        print(f'Hi my name is {self.name}')

    @staticmethod
    def say_hello():
        print('hello')


p = Person('J.K.')

Person.say_hello()
p.say_hello()

Upvotes: 2

Related Questions