Python: asignar un método estático a una variable de clase genera un error

Quiero asignar un método estático a una variable de clase en Python, y a continuación se muestra mi código.

class Klass: classVariable = None @staticmethod def method(): print "method called" Klass.classVariable = Klass.method Klass.method() Klass.classVariable() 

Esto me dio un error en la última línea, TypeError: unbound method method() must be called with Klass instance as first argument (got nothing instead).

Pero cuando cambio el método estático al método de clase, funciona. ¿Alguien puede darme alguna idea de por qué este es el caso?

Backstory (protocolo descriptor)

Primero, necesitamos saber un poco acerca de los descriptores de python …

Para esta respuesta, debería ser suficiente saber lo siguiente:

  1. Las funciones son descriptores.
  2. El comportamiento de vinculación de los métodos (es decir, cómo un método sabe qué pasar) se implementa a través del método __get__ la función y el protocolo descriptor incorporado.
  3. Cuando pones un descriptor foo en una clase, acceder al descriptor realmente llama al método .__get__ . (Esto es realmente una generalización de la statement 2)

En otras palabras:

 class Foo(object): val = some_descriptor 

Cuando lo hago:

 result = Foo.val 

Python realmente hace:

 Foo.val.__get__(None, Foo) 

Cuando lo hago:

 f = Foo() f.val 

python hace:

 f = Foo() type(f).val.__get__(f, type(f)) 

Ahora lo bueno.

Parece que (en python2.x), el staticmethod se implementa de tal manera que el método __get__ devuelve una función normal. Puedes ver esto imprimiendo el tipo de Klass.method :

 print type(Klass.method) #  

Entonces, lo que hemos aprendido es que el método devuelto por Klass.method.__get__ es simplemente una función regular.

Cuando pones esa función regular en una clase, el método __get__ devuelve un método de __get__ (que espera un argumento self ). Esto no es sorprendente … Lo hacemos todo el tiempo:

 class Foo(object): def bar(self): print self 

No es diferente de python que:

 def bar(self): print self class Foo(object): pass Foo.bar = bar 

excepto que la primera versión es mucho más fácil de leer y no desordena el espacio de nombres de su módulo.

Así que ahora hemos explicado cómo su método estático se convirtió en un método de instancia. Hay algo que podamos hacer al respecto?

Solución

Cuando coloque el método en la clase, designe nuevamente como staticmethod y funcionará Ok.

 class Klass(object): # inheriting from object is a good idea. classVariable = None @staticmethod def method(): print("method called") Klass.classVariable = staticmethod(Klass.method) # Note extra staticmethod Klass.method() Klass.classVariable() 

Apéndice – @staticmethod de @staticmethod

Si eres un poco pequeño pero tienes curiosidad sobre cómo puedes implementar el staticmethod para no tener este problema, aquí tienes un ejemplo:

 class StaticMethod(object): def __init__(self, fn): self.fn = fn def __get__(self, inst, cls): return self def __call__(self, *args, **kwargs): return self.fn(*args, **kwargs) class Klass(object): classVariable = None @StaticMethod def method(): print("method called") Klass.classVariable = Klass.method Klass.method() Klass.classVariable() Klass().method() Klass().classVariable() 

El truco aquí es que mi __get__ no devuelve una función. Se vuelve a sí mismo . Cuando lo pones en una clase diferente (o en la misma clase), __get__ se devolverá solo. Dado que se está devolviendo desde __get__ , debe pretender ser una función (para que pueda llamarse después de “__gotten__”), así que implemento un método __call__ personalizado para hacer lo correcto (pase a la función de delegado y devuelva el resultado ).

Tenga en cuenta que no estoy abogando por utilizar este staticmethod lugar del staticmethod . Será menos eficiente y no tan introspectible (y probablemente confuso para sus lectores de código). Esto es sólo para fines educativos.