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

19

Let's say I have the following class:

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

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

    
asked by flpn 09.10.2017 в 19:08

3 answers

8

This has some relation to the instance and class attributes, along with the construction of the Python language, more precisely the decorators. Unlike other languages, the definition of a static method - or class, using @classmethod - is done through a decorator, not a language key word. What does that change? It changes that when using a decorator on a method, the reference to this method is not directly accessible, since the return of the decorator 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 at Ideone | Repl.it

The output will be:

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

And not method , as expected. To better understand the behavior, just remember the operation of a decorator: a function that returns another function (roughly). 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 @staticmethod , but it is 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 because it is a class attribute, it maintains the reference to the same object in both the class and 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 at Ideone | Repl.it

Output is something like:

47914624982008
47914624982008

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

And why is not 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 defined implicitly 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 - no such parameter is passed. because it does not make sense to access an instance in a static method).

We can imagine staticmethod as something like:

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

Notice that in the call of func , which would be the invocation ( of evil ) method, there is no parameter equivalent to self being passed.

But what if you need to access class attributes?

If within the method it is necessary to access the class attributes it is not a static method that we need, but rather a class method. Unlike the static method, the class method receives 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, unlike @staticmethod , in @classmethod , there is the class pass as the first parameter. It would look something like:

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

I think this is because @staticmethod just does not make the first method argument in 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()

It will cause the error:

  

TypeError: hello () missing 1 required positional argument: 'self'

Since Python has no visibility ( public , private , protected ) then it is possible to access externally to variables and methods, another detail is that it could be used in other languages too (what varies from language to languages, some 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 ()' can not 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 in the instance (depending on the language), since it is not possible to access the "self".

How does self work in method

Then returning to Python and to summarize, what distinguishes is instance access, within @staticmethod the first argument of a method will not be self , for example if you pass this:

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

Bar().foo('teste')

Will display something like:

<__main__.Bar object at 0x02061150>
teste

Then self can be written anyway, that the first argument of the method will always be "instance access" ( self ), now pass @staticmethod the first argument will be a de facto argument, for example:

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

Bar().foo('teste')

It will be displayed:

teste
None

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

    
09.10.2017 в 19:33
1

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

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

Because static methods do not depend on an instance of the object to be called, the% auto_reference 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!");

Tests:

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.
    
09.10.2017 в 19:56