Why static methods can be called through the "instance" of the Python 3 class?

Asked

Viewed 719 times

20

Let’s say I have the following class:

class Person:
    @staticmethod
    def hello():
        print('Hello!)

When performing the operation Person().hello() the method is executed normally. But the method hello belongs to the Person class, not to an instance of the class. Could anyone tell me why this instruction works?

  • 2

    I believe that it falls into the usual "because the language creators thought it was useful". It may be that they did not want to complicate the compiler. It may even be, although improbable, that they did not pay attention to it. There are those who think that this is not good because it is not the right intention. Tapping the eye looks like an instance method. But in languages like Python, it doesn’t care much about this. For example, you don’t know if `Person() is a constructor of any object or function. Language is not concerned with showing intention more clearly ,so this is considered normal in it.

  • 2

    Strangely, in Java it is also possible to call static methods through instances

  • 3

    Here, Jon Skeet says he believes it was a mistake of the Java developers, maybe it also applies to Python (I dare not put it as a response haha)

  • What I find strange is that in every nonstatic method in Python, the instance itself is passed as the first parameter of the method (self). Since the hello method does not receive any parameter, this should cause an error.

  • I don’t know much about the particularities of Python, but is this self not passed anyway? Just as we have the arguments javascript

  • The self (which is actually the object that is trying to access the method) is implicitly passed to all methods. It may be that in the static methods this does not happen, another doubt that I have.

Show 1 more comment

3 answers

11


This has, in a way, a relationship with the attributes of instance and class, along with the construction of the Python language, more precisely the decorators. Unlike other languages, the definition of a static - or class method, using @classmethod - is given through a decorator, not a language keyword. What does it change? It changes that when using a decorator over a method, the reference to this method is not directly accessible, because the decorator’s return is a function - and it is this function that invokes the method reference. You can confirm this by doing:

class Person:
    @staticmethod
    def hello():
        print('Hello!')

print(type(Person.hello))
print(type(Person().hello))

See working on Ideone | Repl.it

The exit will be:

<class 'function'>
<class 'function'>

And not method, as expected. To better understand the behavior, just remember the functioning of a decorator: a function that returns another function (roughly speaking). So much so that it is possible to call the decorator explicitly.

class Person:
    def __hello():
        print('Hello!')
    hello = staticmethod(__hello)

This code, for practical purposes, is equivalent to the previous one, which uses the @staticmethod, but it becomes clear that what is created is a class attribute called hello, which is a function, return of the decorator. Being a class attribute, it will also be accessible in instances - and since it is a class attribute, it keeps reference to the same object both in the class and in the instance. We can confirm this by checking the id of each:

class Person:
    @staticmethod
    def hello():
        print('Hello!')

print(id(Person.hello))
print(id(Person().hello))

See working on Ideone | Repl.it

The exit is something like:

47914624982008
47914624982008

Which indicates that objects accessed by the class or instance are exactly the same.

And why is it not past self per parameter?

Precisely because the object does not point to a method, but rather to a function (which is the return of the decorator). The first parameter, self, is implicitly defined in the call of a method by Python, however, as in this case what happens is the call of a function - and it is the function that invokes the method - is not passed such parameter (even because it makes no sense to access an instance in a static method).

We can imagine the staticmethod as being something like:

def staticmethod(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
    return wrapper

Note that on the call from func, that would be the invocation (of evil) of the method, there is no equivalent parameter to self being passed on.

But what if you need to access class attributes?

If within the method it is necessary to access class attributes it is not a static method we need, but a class method. Unlike the static method, the class method takes as its first parameter an object that is the reference to the class itself:

class Person:
    attr = "SOpt"

    @classmethod
    def hello(cls):
        print('Hello', cls.attr)

This happens because, different from @staticmethod, in the @classmethod there is the class passed as the first parameter. It would be something like:

def classmethod(func)
    def wrapper(*args, **kwargs):
        classObject = inspect.get_glass(func) # hipotético
        func(classObject, *args, **kwargs)
    return wrapper
  • 2

    Well explained, thank you!

3

I believe this is because the @staticmethod just doesn’t turn the first argument of the method into "self", for example if you try something like:

class Person:
    x = 1

    def foo(self, bar):
        x = bar

    @staticmethod
    def hello(self):
        print(self.x)

p = Person()
p.foo(2017)
p.hello()

Will cause the mistake:

Typeerror: hello() Missing 1 required positional argument: 'self'

Since Python has no visibility (public, private, protected) then it is possible to access externally variables and methods, another detail is that it would be possible to use in other languages also (which varies from language to languages, some emit warnings others do not accept), example in PHP that accepts:

<?php
class Person {
    static function hello() {
        print('Olá');
    }
}

$p = new Person();
$p->hello();

Already in C# this would cause access problem, for example:

class Exemplo {
    public int foo() { return 1; }
    public static int bar() { return 42; }
}

Exemplo exemplo = new Exemplo();
Console.WriteLine(exemplo.foo());
Console.WriteLine(exemplo.bar());

Static Member `Example.bar()' cannot be accessed with an instance Reference, qualify it with a type name Instead

Then in static it does not seem to be an error to access directly at the instance (depending on the language), since it is not possible to access the "self".


Like the self works in the method

So back to Python and to summarize, what differentiates is the access to instance, inside the @staticmethod the first argument of a method will not be the self, for example if this happens:

class Bar:
    def foo(maracuja, baz=None):
        print(maracuja)
        print(baz)

Bar().foo('teste')

It’ll display something like:

<__main__.Bar object at 0x02061150>
teste

So self can be written anyway, that the first argument of the method will always be "access to" (the self), now freak out @staticmethod the first argument will be a factual argument, for example:

class Bar:
    @staticmethod
    def foo(maracuja, baz=None):
        print(maracuja)
        print(baz)

Bar().foo('teste')

Will be shown:

teste
None

Access Bar().foo() or Bar.foo() will have the same level of access, ie do not access the instance, the writing is independent and does not affect, probably by language design decision.

  • The word self is not a reserved word, it is used only by convention. What matters is what it means: the instance. The self is implicitly passed to all non-static methods, so in this example of yours, the self is just another common argument and as you did not pass that argument at the time of the method call, it did accuse the error. In short, you have a method waiting for an argument but you called it without.

  • @flpn self implicit in Python? Are we talking about the same language? I’m almost certain that self is required in the methods used in the instance. PS: use Python 3.6. Anyway I will give a better read on the documentation.

  • 1

    Yes, the first parameter (self, or any other name you want to give) is implicitly past but it is explicitly received. So when calling a method we don’t need to pass the parameter self, but when declaring a method we have to necessarily put it in the list of parameters.

  • 2

    @flpn so imagine a method without staticmethod thus def baz(x): what the value of x? He’ll be something like: <__main__.Person object at 0x002B6350>, or you can write self, or xyz that will be the same thing. The only way to prevent the self (independent of the name) is to use staticmethod, I could tell?

  • 2

    Yes, but I didn’t disagree with that at any time. I just don’t understand how it can cure my doubt.

  • 1

    @flpn because if "without" staticmethod the first argument becomes "self", with staticmethod the first argument nay becomes the "self", so this is the use of the staticmethod. Got it? Now about the Foo().bar() work I mentioned in the answer, it is because it will remain static and will be the same thing as Foo.bar(), because in none of them it will be possible to access self.

Show 1 more comment

1

Class methods marked with @staticmethod are considered static, i.e., do not depend on an instance of the object to be called.

However, this does not mean that calling static methods through an object instance is invalid.

How static methods do not depend on an instance of the object to be called, self-reference self is not passed to the method and is not valid within the scope of static methods.

Consider the following class:

class Cachorro:

    def __init__( self, nome ):
        self.nome = nome;
        print("Cachorro construido!")

    def obterNome(self):
        return self.nome;

    @staticmethod
    def latir():   # Metodos estaticos nao possuem a auto-referencia 'self'
        print("!Au! au!");

Testing:

c = Cachorro("Astro")      # Criando instancia de Cachorro

c.latir()                  # OK! Apesar de estatico, latir() pode ser chamado a partir de uma instancia de Cachorro

print c.obterNome()        # OK! O metodo obterNome() NAO EH estatico.

Cachorro.latir()           # OK! O metodo latir() eh estatico.

print Cachorro.obterNome() # ERRO! O metodo obterNome() NAO EH estatico.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.