Diferencias de métodos de clase en Python: vinculado, no vinculado y estático
¿Cuál es la diferencia entre los siguientes métodos de clase?
¿Será 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()
En Python, existe 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 vinculada
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 method_two
fallará 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, consulte esta pregunta ) que no cree métodos vinculados para method_two
.
Ahora, puedes invocar un método estático tanto en una instancia como directamente en la clase:
>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two
Los métodos en Python son algo muy, muy simple una vez que entiendes los conceptos básicos del sistema de descriptores. 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 foo
atributo de la clase, obtendrá 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.foo
es aproximadamente 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 tienes una instancia de una clase, es casi lo mismo, solo que esa None
es la instancia de la clase:
>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>
Ahora bien, ¿por qué Python hace eso? Porque el objeto del método vincula el primer parámetro de una función a la instancia de la clase. De ahí viene el yo. Ahora bien, a veces no quieres que tu clase convierta una función en un método, ahí es donde staticmethod
entra en juego:
class C(object):
@staticmethod
def foo():
pass
El staticmethod
decorador envuelve su clase e implementa un modelo __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.
Cuando llamas a un miembro de la clase, Python usa automáticamente una referencia al objeto como primer parámetro. La variable self
en realidad no significa nada, es sólo una convención de codificación. Podrías llamarlo gargaloo
si quisieras. Dicho esto, la llamada a method_two
generaría un TypeError
, porque Python intenta automáticamente pasar un parámetro (la referencia a su objeto principal) a un método que se definió como sin parámetros.
Para que funcione, puedes agregar esto a tu definición de clase:
method_two = staticmethod(method_two)
o podrías usar el @staticmethod
decorador de funciones .