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
owner
es 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 foo
no 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.