classmethod - python 3 descriptors
¿Por qué @staticmethod no se conserva en todas las clases, cuando @classmethod es? (2)
DESCARGO DE RESPONSABILIDAD: Esta no es realmente una respuesta, pero tampoco encaja en un formato de comentario.
Tenga en cuenta que con Python2 @classmethod @classmethod
se conserva correctamente en todas las clases. En el siguiente código, la llamada a B.one()
funciona como si se invocó a través de la clase A
:
$ cat test.py
class A(object):
@classmethod
def one(cls):
print("I am class", cls.__name__)
class A2(A):
pass
class B(object):
one = A.one
A.one()
A2.one()
B.one()
$ python2 test.py
(''I am class'', ''A'')
(''I am class'', ''A2'')
(''I am class'', ''A'')
Tome el siguiente script de ejemplo:
class A(object):
@classmethod
def one(cls):
print("I am class")
@staticmethod
def two():
print("I am static")
class B(object):
one = A.one
two = A.two
B.one()
B.two()
Cuando ejecuto este script con Python 2.7.11 obtengo:
I am class
Traceback (most recent call last):
File "test.py", line 17, in <module>
B.two()
TypeError: unbound method two() must be called with B instance as first argument (got nothing instead)
Parece que el decorador de @classmethod se conserva en todas las clases, pero @staticmethod no lo está.
Python 3.4 se comporta como se esperaba:
I am class
I am static
¿Por qué Python2 no preserva el método @static, y hay una solución alternativa?
editar: tomar dos de una clase (y retener @staticmethod) parece funcionar, pero esto todavía me parece extraño.
classmethod
y staticmethod
son descriptores, y ninguno de ellos está haciendo lo que espera, no solo staticmethod
.
Cuando accedes a A.one
, está creando un método enlazado en A
, luego haciendo que sea un atributo de B
, pero como está vinculado a A
, el argumento cls
siempre será A
, incluso si llamas a B.one
(este es el caso) en Python 2 y Python 3; está mal en todos lados).
Cuando accedes a A.two
, devuelve el objeto de función raw (el descriptor de staticmethod
no necesita hacer nada especial además de evitar el enlace que pasaría self
o cls
, por lo que solo devuelve lo que envuelve). Pero ese objeto de función sin staticmethod
se adjunta a B
como un método de instancia staticmethod
, porque sin el envoltorio de los elementos staticmethod
, es como si lo hubieras definido normalmente.
La razón por la cual este último funciona en Python 3 es que Python 3 no tiene ningún concepto de métodos independientes. Tiene funciones (que si se accede a través de una instancia de una clase se vuelven métodos vinculados) y métodos vinculados, donde Python 2 tiene funciones, métodos independientes y métodos vinculados.
Los métodos sin consolidar comprueban que se invocan con un objeto del tipo correcto y, por lo tanto, con su error. Las funciones simples solo quieren la cantidad correcta de argumentos.
El decorador de staticmethod
en Python 3 todavía está devolviendo el objeto de función sin procesar, pero en Python 3, está bien; dado que no es un objeto de método desatado especial, si lo llamas a la clase en sí, es como una función de espacio de nombres, no un método de ningún tipo. Verías el problema si intentaras hacer:
B().two()
sin embargo, porque eso hará que un método vinculado de esa instancia de B
y las two
funciones, pasando un argumento extra ( self
) que two
no acepte. Básicamente, en Python 3, staticmethod
es una conveniencia que te permite llamar a la función en instancias sin causar un enlace, pero si solo llamas a la función al hacer referencia a la clase, no es necesaria, porque es solo una función simple, no el Python 2 "método sin consolidar".
Si tiene alguna razón para realizar esta copia (normalmente, le sugiero que herede de A
, pero lo que sea), y quiere asegurarse de obtener la versión de la función ajustada al descriptor, no lo que le indique el descriptor cuando acceda a A
, __dict__
el protocolo del descriptor al acceder directamente a A
''s __dict__
:
class B(object):
one = A.__dict__[''one'']
two = A.__dict__[''two'']
Copiando directamente desde el diccionario de la clase, nunca se invoca el protocolo descriptor magic, y obtienes las staticmethod
classmethod
de one
y two
del staticmethod
y de la classmethod
.