Diferencias de método de clase en Python: enlazado, desatado y estático

242

¿Cuál es la diferencia entre los siguientes métodos de clase?

¿Es que uno es estático y el otro no?

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()
Franck Mesirard
fuente
18
No hay otra diferencia que la definición de method_two () no es válida y su llamada falla.
anatoly techtonik
14
@techtonik: ¡No hay nada malo en la definición del método_dos! Se llama en una especificación incorrecta / no válida, es decir, con un argumento adicional.
0xc0de
1
Los suyos son métodos de instancia , no métodos de clase. Crea un método de clase aplicando @classmethoda la definición. Se debe llamar al primer parámetro en clslugar de selfy recibirá el objeto de clase en lugar de una instancia de su clase: Test.method_three()y a_test.method_three()son equivalentes.
Lutz Prechelt el
¿Por qué querrías crear una definición de función sin el selfargumento? ¿Hay un fuerte caso de uso para esto?
alpha_989

Respuestas:

412

En Python, hay una distinción entre métodos vinculados y no vinculados .

Básicamente, una llamada a una función miembro (como method_one), una función enlazada

a_test.method_one()

se traduce a

Test.method_one(a_test)

es decir, una llamada a un método independiente. Debido a eso, una llamada a su versión de method_twofallará con unTypeError

>>> 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) 

Puedes cambiar el comportamiento de un método usando un decorador

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

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

El decorador le dice a la metaclase predeterminada incorporada type(la clase de una clase, cf. esta pregunta ) que no cree métodos vinculados method_two.

Ahora, puede invocar el método estático tanto en una instancia como en la clase directamente:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two
Torsten Marek
fuente
17
Apoyo esta respuesta, es superior a la mía. Bien hecho Torsten :)
espacio libre
24
en python 3 los métodos no vinculados están en desuso. en cambio solo hay una función.
boldnik
@boldnik, ¿por qué dice que los métodos no vinculados están en desuso? los métodos estáticos todavía están presentes en la documentación: docs.python.org/3/library/functions.html#staticmethod
alpha_989
195

Los métodos en Python son una cosa muy, muy simple una vez que comprende los conceptos básicos del sistema descriptor. Imagina la siguiente clase:

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

Ahora echemos un vistazo a esa clase en el shell:

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

Como puede ver si accede al fooatributo en la clase, obtiene un método independiente, sin embargo, dentro del almacenamiento de la clase (el dict) hay una función. ¿Porque eso? La razón de esto es que la clase de su clase implementa un __getattribute__que resuelve descriptores. Suena complejo, pero no lo es. C.fooes más o menos equivalente a este código en ese caso especial:

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

Esto se debe a que las funciones tienen un __get__método que las convierte en descriptores. Si tiene una instancia de una clase, es casi la misma, solo que Nonees la instancia de la clase:

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

Ahora, ¿por qué Python hace eso? Porque el objeto del método une el primer parámetro de una función a la instancia de la clase. De ahí viene el yo. Ahora a veces no quieres que tu clase convierta una función en un método, ahí es donde staticmethodentra en juego:

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

El staticmethoddecorador envuelve su clase e implementa un ficticio __get__que devuelve la función envuelta como función y no como método:

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

Espero que eso lo explique.

Armin Ronacher
fuente
12
El staticmethoddecorador ajusta su clase (...) Esta frase es un poco engañosa ya que la clase que se está envolviendo es la clase del método fooy no la clase en la que foose define.
Piotr Dobrogost
12

Cuando llama a un miembro de la clase, Python usa automáticamente una referencia al objeto como primer parámetro. La variable en selfrealidad no significa nada, es solo una convención de codificación. Podrías llamarlo gargaloosi quisieras. Dicho esto, la llamada a method_twoelevaría a TypeError, porque Python intenta automáticamente pasar un parámetro (la referencia a su objeto padre) a un método que se definió como que no tiene parámetros.

Para que realmente funcione, puede agregar esto a su definición de clase:

method_two = staticmethod(method_two)

o puedes usar la @staticmethod función decorador .

Justin Poliey
fuente
44
Te refieres a "la sintaxis del decorador de la función @staticmethod".
tzot
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
kzh
fuente
2
Class.instance_method() # The class cannot use an instance methodpuede usar Simplemente pase la instancia manualmente:Class.instance_method(a)
warvariuc
@warwaruk Está ahí, mira la línea debajo de la TyeErrorlínea.
kzh
Sí, lo vi más tarde. Aún así, no es correcto decir 'La clase no puede usar un método de instancia', porque lo hiciste una línea a continuación.
warvariuc
@kzh, gracias por tu explicación. Cuando llamó a.class_method(), parece que a.cse actualizó a 1, así que la llamada Class.class_method(), actualizó la Class.cvariable a 2. Sin embargo, cuando lo asignó a.c=5, ¿por qué Class.cno se actualizó 5?
alpha_989
@ alpha_989 python primero busca un atributo directamente en la propia instancia de un objeto y, si no está allí, lo busca en su clase de manera predeterminada. Si tiene alguna otra pregunta sobre esto, siéntase libre de abrir una pregunta y vincularla aquí y con gusto lo ayudaré.
kzh
4

method_two no funcionará porque estás definiendo una función miembro pero no le estás diciendo de qué es miembro la función. Si ejecuta la última línea obtendrá:

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

Si está definiendo funciones miembro para una clase, el primer argumento siempre debe ser 'self'.

Jon Cage
fuente
3

Explicación precisa de Armin Ronacher arriba, ampliando sus respuestas para que los principiantes como yo lo entiendan bien:

La diferencia en los métodos definidos en una clase, ya sea método estático o de instancia (hay otro tipo - método de clase - no discutido aquí, así que omitiéndolo), radica en el hecho de si de alguna manera están vinculados a la instancia de clase o no. Por ejemplo, diga si el método recibe una referencia a la instancia de clase durante el tiempo de ejecución

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

La __dict__propiedad de diccionario del objeto de clase contiene la referencia a todas las propiedades y métodos de un objeto de clase y, por lo tanto,

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

El método foo es accesible como el anterior. Un punto importante a tener en cuenta aquí es que todo en Python es un objeto y, por lo tanto, las referencias en el diccionario anterior apuntan a otros objetos. Permítanme llamarlos objetos de propiedad de clase, o como CPO dentro del alcance de mi respuesta por brevedad.

Si un CPO es un descriptor, el intérprete de Python llama al __get__()método del CPO para acceder al valor que contiene.

Para determinar si un CPO es un descriptor, el intérprete de Python verifica si implementa el protocolo del descriptor. Implementar el protocolo descriptor es implementar 3 métodos

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

por ej.

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

dónde

  • self es el CPO (podría ser una instancia de list, str, function, etc.) y lo proporciona el tiempo de ejecución
  • instance es la instancia de la clase en la que se define este CPO (el objeto 'c' anterior) y debe ser provisto explícitamente por nosotros
  • owneres la clase donde se define este CPO (el objeto de clase 'C' anterior) y necesita que lo suministremos nosotros. Sin embargo, esto se debe a que lo estamos llamando al CPO. cuando lo llamamos en la instancia, no necesitamos suministrar esto ya que el tiempo de ejecución puede suministrar la instancia o su clase (polimorfismo)
  • value es el valor previsto para el CPO y debe ser suministrado por nosotros

No todos los CPO son descriptores. Por ejemplo

>>> 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__'

Esto se debe a que la clase de lista no implementa el protocolo descriptor.

Por lo tanto, el argumento self in c.foo(self)se requiere porque su firma de método es realmente esto C.__dict__['foo'].__get__(c, C)(como se explicó anteriormente, C no es necesario, ya que se puede encontrar o polimorfizar) Y esta es también la razón por la que obtiene un TypeError si no pasa ese argumento de instancia requerido.

Si observa que todavía se hace referencia al método a través de la clase Objeto C y el enlace con la instancia de clase se logra al pasar un contexto en la forma del objeto de instancia a esta función.

Esto es bastante sorprendente, ya que si opta por no mantener ningún contexto o ningún enlace a la instancia, todo lo que se necesitaba era escribir una clase para ajustar el CPO del descriptor y anular su __get__()método para no requerir contexto. Esta nueva clase es lo que llamamos un decorador y se aplica a través de la palabra clave@staticmethod

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

La ausencia de contexto en el nuevo CPO envuelto foono arroja un error y se puede verificar de la siguiente manera:

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

El caso de uso de un método estático es más un espacio de nombres y de mantenimiento de código (sacarlo de una clase y ponerlo a disposición en todo el módulo, etc.).

Tal vez sea mejor escribir métodos estáticos en lugar de métodos de instancia siempre que sea posible, a menos que, por supuesto, necesite contexualizar los métodos (como variables de instancia de acceso, variables de clase, etc.). Una razón es facilitar la recolección de basura al no mantener referencias no deseadas a los objetos.

supi
fuente
1

Eso es un error.

en primer lugar, la primera línea debería ser así (tenga cuidado con las mayúsculas)

class Test(object):

Cada vez que llama a un método de una clase, se convierte en el primer argumento (de ahí el nombre self) y method_two da este error

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

El segundo no funcionará porque cuando lo llamas así, python intenta internamente llamarlo con la instancia a_test como primer argumento, pero tu method_two no acepta ningún argumento, por lo que no funcionará, obtendrás un tiempo de ejecución error. Si desea el equivalente de un método estático, puede usar un método de clase. Hay mucha menos necesidad de métodos de clase en Python que los métodos estáticos en lenguajes como Java o C #. La mayoría de las veces, la mejor solución es usar un método en el módulo, fuera de una definición de clase, que funcionan de manera más eficiente que los métodos de clase.

Vasil
fuente
Si defino una función Class Test(object): @staticmethod def method_two(): print(“called method_two”) Un caso de uso, en el que estaba pensando es cuando desea que la función sea parte de una clase, pero no desea que el usuario acceda a la función directamente. Por lo que la method_twopuede llamar otras funciones dentro de la Testinstancia, pero no puedo ser llamado usando a_test.method_two(). Si lo uso def method_two(), ¿funcionará para este caso de uso? ¿O hay una mejor manera de modificar la definición de la función, para que funcione según lo previsto para el caso de uso anterior?
alpha_989
1

La llamada a method_two arrojará una excepción por no aceptar el parámetro propio, el tiempo de ejecución de Python lo pasará automáticamente.

Si desea crear un método estático en una clase de Python, decórelo con el staticmethod decorator.

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

Test.method_two()
MvdD
fuente
1

Lea estos documentos de Guido First Class. Todo explica claramente cómo nacen los métodos independientes y vinculados.

James Sapam
fuente
0

La definición de method_twono es válida. Cuando llame method_two, recibirá TypeError: method_two() takes 0 positional arguments but 1 was givendel intérprete.

Un método de instancia es una función acotada cuando lo llamas como a_test.method_two(). Acepta automáticamente self, que apunta a una instancia de Test, como su primer parámetro. A través del selfparámetro, un método de instancia puede acceder libremente a los atributos y modificarlos en el mismo objeto.

Yossarian42
fuente
0

Métodos independientes

Los métodos no vinculados son métodos que aún no están vinculados a ninguna instancia de clase en particular.

Métodos vinculados

Los métodos vinculados son los que están vinculados a una instancia específica de una clase.

Como se documenta aquí , self puede referirse a diferentes cosas dependiendo de que la función esté vinculada, no vinculada o estática.

Eche un vistazo al siguiente ejemplo:

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 no utilizamos la instancia de clase obj- en la última llamada, podemos decir que parece un método estático.

Si es así, ¿cuál es la diferencia entre una MyClass.some_method(10)llamada y una llamada a una función estática decorada con un @staticmethoddecorador?

Al usar el decorador, dejamos explícitamente claro que el método se usará sin crear primero una instancia para él. Normalmente uno no esperaría que los métodos de los miembros de la clase se usen sin la instancia y acceder a ellos puede causar posibles errores dependiendo de la estructura del método.

Además, al agregar el @staticmethoddecorador, también estamos haciendo posible que se pueda llegar a través de un 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

No puede hacer el ejemplo anterior con los métodos de instancia. Puede sobrevivir al primero (como lo hicimos antes), pero el segundo se traducirá en una llamada no vinculada MyClass.some_method(obj, 10)que generará una, TypeErrorya que el método de instancia toma un argumento y usted intentó pasar dos sin intención.

Entonces, podría decir: "si puedo llamar a métodos estáticos a través de una instancia y una clase, MyClass.some_static_methody MyClass().some_static_methoddeberían ser los mismos métodos". ¡Si!

alexa
fuente
0

Método enlazado = método de instancia

Método independiente = método estático.

Python Newbie
fuente
Agregue alguna explicación adicional a su respuesta para que otros puedan aprender de ella. Por ejemplo, eche un vistazo a las otras respuestas a esta pregunta
Nico Haase