Diferenças de método de classe em Python: bound, unbound e static


242

Qual é a diferença entre os seguintes métodos de classe?

Será que um é estático e o outro não?

class Test(object):
  def method_one(self):
    print "Called method_one"

  def method_two():
    print "Called method_two"

a_test = Test()
a_test.method_one()
a_test.method_two()

18
Nenhuma diferença diferente da definição method_two () é inválida e sua chamada falha.
Anatoly techtonik

14
@techtonik: Nada está errado com a definição do method_two! Ele está sendo chamado com uma especificação incorreta / inválida, ou seja, com um argumento extra.
0xc0de 10/09/2013

1
Os dois são métodos de instância , não métodos de classe. Você cria um método de classe aplicando @classmethodà definição. O primeiro parâmetro deve ser chamado em clsvez de selfe receberá o objeto de classe em vez de uma instância de sua classe: Test.method_three()e a_test.method_three()é equivalente.
Lutz Prechelt 4/08/16

Por que você deseja criar uma definição de função sem o selfargumento? Existe um forte caso de uso para isso?
alpha_989

Respostas:


412

No Python, há uma distinção entre métodos vinculado e não vinculado .

Basicamente, uma chamada para uma função membro (como method_one), uma função vinculada

a_test.method_one()

é traduzido para

Test.method_one(a_test)

ou seja, uma chamada para um método não vinculado. Por esse motivo, uma chamada para sua versão method_twofalhará com umTypeError

>>> a_test = Test() 
>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given) 

Você pode alterar o comportamento de um método usando um decorador

class Test(object):
    def method_one(self):
        print "Called method_one"

    @staticmethod
    def method_two():
        print "Called method two"

O decorador diz à metaclasse padrão interna type(a classe de uma classe, veja esta pergunta ) para não criar métodos vinculados method_two.

Agora, você pode invocar o método estático em uma instância ou diretamente na classe:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two

17
Eu apoio esta resposta, é superior à minha. Bem feito Torsten :)
freespace

24
no python 3 métodos não acoplados são preteridos. em vez disso, há apenas uma função.
Boldnik # 30/13

@oldnik, por que você diz que métodos não acoplados estão obsoletos? métodos estáticos ainda estão presentes na documentação: docs.python.org/3/library/functions.html#staticmethod
alpha_989

195

Os métodos em Python são uma coisa muito, muito simples, depois que você entende o básico do sistema descritor. Imagine a seguinte classe:

class C(object):
    def foo(self):
        pass

Agora vamos dar uma olhada nessa classe no shell:

>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

Como você pode ver se você acessa o fooatributo na classe, obtém um método não acoplado, mas dentro do armazenamento da classe (o dict) existe uma função. Por que isso? A razão para isso é que a classe da sua classe implementa uma __getattribute__que resolve descritores. Parece complexo, mas não é. C.fooé aproximadamente equivalente a esse código nesse caso especial:

>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>

Isso ocorre porque as funções têm um __get__método que as torna descritores. Se você tem uma instância de uma classe, é quase a mesma, apenas essa Noneé a instância da classe:

>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>

Agora, por que o Python faz isso? Como o objeto de método liga o primeiro parâmetro de uma função à instância da classe. É daí que vem o eu. Agora, às vezes, você não quer que sua classe torne uma função um método, é aí que staticmethodentra em jogo:

 class C(object):
  @staticmethod
  def foo():
   pass

O staticmethoddecorador agrupa sua classe e implementa um manequim __get__que retorna a função agrupada como função e não como método:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Espero que isso explique.


12
O staticmethoddecorador agrupa sua classe (...) Essa frase é um pouco enganadora, pois a classe que está sendo agrupada é a classe do método fooe não a classe na qual fooestá definida.
Piotr Dobrogost

12

Quando você chama um membro da classe, o Python automaticamente usa uma referência ao objeto como o primeiro parâmetro. A variável selfrealmente não significa nada, é apenas uma convenção de codificação. Você poderia ligar gargaloose quisesse. Dito isso, a chamada para method_twogeraria um TypeError, porque o Python está automaticamente tentando passar um parâmetro (a referência ao seu objeto pai) para um método que foi definido como sem parâmetros.

Para realmente fazê-lo funcionar, você pode anexar isso à sua definição de classe:

method_two = staticmethod(method_two)

ou você pode usar o @staticmethod decorador de funções .


4
Você quer dizer "a sintaxe do decorador da função @staticmethod".
tzot 22/09/08

11
>>> class Class(object):
...     def __init__(self):
...         self.i = 0
...     def instance_method(self):
...         self.i += 1
...         print self.i
...     c = 0
...     @classmethod
...     def class_method(cls):
...         cls.c += 1
...         print cls.c
...     @staticmethod
...     def static_method(s):
...         s += 1
...         print s
... 
>>> a = Class()
>>> a.class_method()
1
>>> Class.class_method()    # The class shares this value across instances
2
>>> a.instance_method()
1
>>> Class.instance_method() # The class cannot use an instance method
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead)
>>> Class.instance_method(a)
2
>>> b = 0
>>> a.static_method(b)
1
>>> a.static_method(a.c) # Static method does not have direct access to 
>>>                      # class or instance properties.
3
>>> Class.c        # a.c above was passed by value and not by reference.
2
>>> a.c
2
>>> a.c = 5        # The connection between the instance
>>> Class.c        # and its class is weak as seen here.
2
>>> Class.class_method()
3
>>> a.c
5

2
Class.instance_method() # The class cannot use an instance methodpode usar. Apenas passe a instância manualmente:Class.instance_method(a)
warvariuc

@warwaruk Está lá, olhe para a linha abaixo da TyeErrorlinha.
kzh

Sim, eu vi depois. ainda assim, não é correto dizer 'A classe não pode usar um método de instância', porque você fez uma linha abaixo.
warvariuc

@ kzh, obrigado pela sua explicação. Quando você ligou a.class_method(), parece que a.cfoi atualizado para 1, então a chamada a Class.class_method()atualizou a Class.cvariável para 2. No entanto, quando você atribuiu a.c=5, por que Class.cnão foi atualizado 5?
alpha_989

@ alpha_989 python primeiro procura por um atributo diretamente na própria instância de um objeto e, se não estiver lá, procura por um atributo em sua classe por padrão. Se você tiver outras dúvidas, sinta-se à vontade para abrir uma pergunta e vinculá-la aqui. Teremos prazer em ajudar.
kzh

4

method_two não funcionará porque você está definindo uma função de membro, mas não está dizendo a ele do que a função é membro. Se você executar a última linha, receberá:

>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

Se você estiver definindo funções-membro para uma classe, o primeiro argumento sempre deve ser 'próprio'.


3

Explicação precisa de Armin Ronacher acima, expandindo suas respostas para que iniciantes como eu entendam bem:

A diferença nos métodos definidos em uma classe, seja estática ou método de instância (ainda existe outro tipo - método de classe - não discutido aqui, para ignorá-lo), reside no fato de eles estarem de alguma forma vinculados à instância de classe ou não. Por exemplo, diga se o método recebe uma referência à instância da classe durante o tempo de execução

class C:
    a = [] 
    def foo(self):
        pass

C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C() 
c # this is the class instance

A __dict__propriedade de dicionário do objeto de classe mantém a referência a todas as propriedades e métodos de um objeto de classe e, portanto,

>>> C.__dict__['foo']
<function foo at 0x17d05b0>

o método foo está acessível como acima. Um ponto importante a ser observado aqui é que tudo em python é um objeto e, portanto, as referências no dicionário acima estão apontando para outros objetos. Deixe-me chamá-los de objetos de propriedade de classe - ou como CPO no escopo da minha resposta, por questões de brevidade.

Se um CPO é um descritor, o interpretador python chama o __get__()método do CPO para acessar o valor que ele contém.

Para determinar se um CPO é um descritor, o interpretador python verifica se implementa o protocolo do descritor. Implementar o protocolo descritor é implementar 3 métodos

def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)

por exemplo

>>> C.__dict__['foo'].__get__(c, C)

Onde

  • self é o CPO (pode ser uma instância de list, str, function etc) e é fornecido pelo tempo de execução
  • instance é a instância da classe em que esse CPO é definido (o objeto 'c' acima) e precisa ser explicitamente fornecido por nós
  • owneré a classe em que esse CPO é definido (o objeto de classe 'C' acima) e precisa ser fornecido por nós. No entanto, isso ocorre porque estamos chamando o CPO. quando o chamamos na instância, não precisamos fornecer isso, pois o tempo de execução pode fornecer a instância ou sua classe (polimorfismo)
  • value é o valor pretendido para o CPO e precisa ser fornecido por nós

Nem todos os CPOs são descritores. Por exemplo

>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510> 
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'

Isso ocorre porque a classe list não implementa o protocolo do descritor.

Portanto, o argumento self in c.foo(self)é necessário porque a assinatura do método é realmente essaC.__dict__['foo'].__get__(c, C) (como explicado acima, C não é necessária, pois pode ser descoberta ou polimorfa). E é também por isso que você obtém um TypeError se não passar esse argumento de instância necessário.

Se você notar que o método ainda é referenciado por meio da classe Objeto C e a ligação com a instância da classe é obtida através da passagem de um contexto na forma do objeto de instância para essa função.

Isso é impressionante, pois se você optou por não manter nenhum contexto ou ligação à instância, tudo o que era necessário era escrever uma classe para agrupar o CPO do descritor e substituir seu __get__()método para não exigir contexto. Essa nova classe é o que chamamos de decorador e é aplicada pela palavra-chave@staticmethod

class C(object):
  @staticmethod
  def foo():
   pass

A ausência de contexto no novo CPO empacotado foonão gera um erro e pode ser verificada da seguinte forma:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

O caso de uso de um método estático é mais um espaço para nome e manutenção de código (removendo-o de uma classe e disponibilizando-o em todo o módulo, etc.).

Talvez seja melhor escrever métodos estáticos do que métodos de instância sempre que possível, a menos que você precise contextualizar os métodos (como acessar variáveis ​​de instância, variáveis ​​de classe etc.). Um dos motivos é facilitar a coleta de lixo, não mantendo referências indesejadas a objetos.


1

isso é um erro.

primeiro de tudo, a primeira linha deve ser assim (tenha cuidado com as maiúsculas)

class Test(object):

Sempre que você chama um método de uma classe, ele aparece como o primeiro argumento (daí o nome self) e method_two gera esse erro

>>> a.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

1

O segundo não funcionará porque, quando você o chama dessa maneira, internamente, o python tenta chamá-lo com a instância a_test como o primeiro argumento, mas seu method_two não aceita nenhum argumento, portanto, não funcionará, você obterá um tempo de execução erro. Se você deseja o equivalente a um método estático, pode usar um método de classe. Há muito menos necessidade de métodos de classe em Python do que métodos estáticos em linguagens como Java ou C #. Na maioria das vezes, a melhor solução é usar um método no módulo, fora de uma definição de classe, aqueles que funcionam com mais eficiência do que os métodos de classe.


Se eu definir uma função Em Class Test(object): @staticmethod def method_two(): print(“called method_two”) um caso de uso, pensei em quando você deseja que a função faça parte de uma classe, mas não deseja que o usuário acesse a função diretamente. Portanto, method_twopode ser chamado por outras funções dentro da Testinstância, mas não pode ser chamado usando a_test.method_two(). Se eu usar def method_two(), isso funcionará para este caso de uso? Ou existe uma maneira melhor de modificar a definição da função, para que ela funcione conforme o pretendido no caso de uso acima?
alpha_989

1

A chamada para method_two lançará uma exceção por não aceitar o auto parâmetro que o tempo de execução do Python passará automaticamente.

Se você deseja criar um método estático em uma classe Python, decore-o com o staticmethod decorator.

Class Test(Object):
  @staticmethod
  def method_two():
    print "Called method_two"

Test.method_two()


0

A definição de method_twoé inválida. Quando você liga method_two, você recebeTypeError: method_two() takes 0 positional arguments but 1 was given do intérprete.

Um método de instância é uma função limitada quando você o chama assim a_test.method_two(). Ele aceita automaticamente self, que aponta para uma instância de Test, como seu primeiro parâmetro. Por meio do selfparâmetro, um método de instância pode acessar livremente atributos e modificá-los no mesmo objeto.


0

Métodos não vinculados

Métodos não ligados são métodos que ainda não estão vinculados a nenhuma instância de classe específica.

Métodos vinculados

Métodos vinculados são aqueles que estão vinculados a uma instância específica de uma classe.

Como está documentado aqui , self pode se referir a coisas diferentes, dependendo da função ser vinculada, não vinculada ou estática.

Veja o seguinte exemplo:

class MyClass:    
    def some_method(self):
        return self  # For the sake of the example

>>> MyClass().some_method()
<__main__.MyClass object at 0x10e8e43a0># This can also be written as:>>> obj = MyClass()

>>> obj.some_method()
<__main__.MyClass object at 0x10ea12bb0>

# Bound method call:
>>> obj.some_method(10)
TypeError: some_method() takes 1 positional argument but 2 were given

# WHY IT DIDN'T WORK?
# obj.some_method(10) bound call translated as
# MyClass.some_method(obj, 10) unbound method and it takes 2 
# arguments now instead of 1 

# ----- USING THE UNBOUND METHOD ------
>>> MyClass.some_method(10)
10

Como não usamos a instância da classe - obj- na última chamada, podemos dizer que parece um método estático.

Em caso afirmativo, qual é a diferença entre uma MyClass.some_method(10)chamada e uma chamada para uma função estática decorada com um @staticmethoddecorador?

Ao usar o decorador, deixamos explicitamente claro que o método será usado sem criar uma instância para ele primeiro. Normalmente, não se espera que os métodos dos membros da classe sejam usados ​​sem a instância e acessá-los pode causar possíveis erros, dependendo da estrutura do método.

Além disso, adicionando o @staticmethoddecorador, estamos possibilitando ser alcançado também através de um objeto.

class MyClass:    
    def some_method(self):
        return self    

    @staticmethod
    def some_static_method(number):
        return number

>>> MyClass.some_static_method(10)   # without an instance
10
>>> MyClass().some_static_method(10)   # Calling through an instance
10

Você não pode fazer o exemplo acima com os métodos da instância. Você pode sobreviver ao primeiro (como fizemos antes), mas o segundo será convertido em uma chamada não acoplada, o MyClass.some_method(obj, 10)que aumentará um, TypeErrorjá que o método de instância recebe um argumento e você, sem querer, tentou passar dois.

Então, você pode dizer: "Se eu puder chamar métodos estáticos através de uma instância e uma classe, MyClass.some_static_methode MyClass().some_static_methoddeve ser o mesmo método". Sim!


0

Método vinculado = método de instância

Método não vinculado = método estático.


Adicione mais explicações à sua resposta para que outras pessoas possam aprender com ela. Por exemplo, dê uma olhada nas outras respostas a esta pergunta
Nico Haase
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.