otra - poo python 3
Diferencias de métodos de clase en Python: enlazado, no enlazado y estático (10)
Cuando llama a un miembro de la clase, Python utiliza automáticamente una referencia al objeto como primer parámetro. La variable self
realidad no significa nada, es solo 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 está intentando automáticamente pasar un parámetro (la referencia a su objeto principal) a un método que se definió como que no tiene parámetros.
Para que funcione, puedes agregar esto a tu definición de clase:
method_two = staticmethod(method_two)
o podrías usar el decorador de la función @staticmethod
.
¿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()
El segundo no funcionará porque cuando lo llamas así, python intenta llamarlo internamente con la instancia a_test como primer argumento, pero 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 utilizar un método de clase. Hay mucha menos necesidad de métodos de clase en Python que métodos estáticos en lenguajes como Java o C #. La mayoría de las veces, la mejor solución es utilizar un método en el módulo, fuera de la definición de clase, que funcione de manera más eficiente que los métodos de clase.
En Python, hay una distinción entre métodos enlazados y no enlazados .
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_two
fallará con un TypeError
>>> 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 utilizando 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 al type
metaclase predeterminado integrado (la clase de una clase, consulte esta pregunta ) que no cree métodos enlazados para 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
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 estático o en un método de instancia (todavía hay otro tipo - método de clase - no se trata aquí, así que se omite), está en el hecho de si están vinculados de alguna manera a la instancia de la 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 propiedad de diccionario __dict__
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 se muestra arriba. Un punto importante a tener en cuenta aquí es que todo en Python es un objeto, por lo que 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, entonces Python Interpreter llama al __get__()
del CPO para acceder al valor que contiene.
Para determinar si un CPO es un descriptor, Python Interpreter comprueba 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 ejemplo
>>> 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 suministrado explícitamente por nosotros -
owner
es la clase donde se define este CPO (el objeto de clase ''C'' arriba) y debemos proporcionarlo nosotros. Sin embargo, esto es porque lo estamos llamando en el 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 debemos proporcionarlo 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 en c.foo(self)
se requiere porque su firma de método es en realidad esta C.__dict__[''foo''].__get__(c, C)
(como se explicó anteriormente, C no es necesario, ya que se puede encontrar) o polimorfado) Y esta es también la razón por la que obtienes un TypeError si no pasas ese argumento de instancia requerido.
Si observa que aún se hace referencia al método a través de la clase Objeto C y el enlace con la instancia de la 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 eligió no tener contexto o no vincularse a la instancia, todo lo que se necesitaba era escribir una clase para envolver el descriptor CPO y anular su __get__()
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 foo
no produce 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 que un espacio de nombres y una capacidad 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 contextualizar los métodos (como variables de instancia de acceso, variables de clase, etc.). Una razón es para facilitar la recolección de basura al no mantener referencias no deseadas a los objetos.
La llamada a method_two lanzará una excepción por no aceptar el parámetro automático, el tiempo de ejecución de Python lo pasará automáticamente.
Si desea crear un método estático en una clase de Python, staticmethod decorator
con el staticmethod decorator
método estático.
Class Test(Object):
@staticmethod
def method_two():
print "Called method_two"
Test.method_two()
Lea esta documentación de Guido First Class todo. Explicó claramente cómo nacen los métodos de Unbound, Bound.
Los métodos en Python son una cosa muy, muy simple una vez que comprendió 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 atributo foo
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 los 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 método __get__
que las convierte en descriptores. Si tiene una instancia de una clase, es casi lo mismo, solo que 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, ¿por qué Python hace eso? Porque el objeto de método enlaza 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 haga de una función un método, ahí es donde entra en juego el método staticmethod
:
class C(object):
@staticmethod
def foo():
pass
El decorador de staticmethod
envuelve su clase e implementa un __get__
ficticio que devuelve la función envuelta como una función y no como un método:
>>> C.__dict__[''foo''].__get__(None, C)
<function foo at 0x17d0c30>
Espero que lo explique.
eso es un error
En primer lugar, la primera línea debe ser así (tenga cuidado con las mayúsculas)
class Test(object):
Cada vez que llama a un método de una clase, se obtiene como 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)
method_two no funcionará porque estás definiendo una función miembro pero no le dices a qué pertenece 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''.
>>> 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