martineau
Cuando intento usar un método estático dentro del cuerpo de la clase y defino el método estático usando el método incorporado staticmethod
funcionar como decorador, así:
class Klass(object):
@staticmethod # use as decorator
def _stat_func():
return 42
_ANS = _stat_func() # call the staticmethod
def method(self):
ret = Klass._stat_func() + Klass._ANS
return ret
Obtuve el siguiente error:
Traceback (most recent call last):
File "call_staticmethod.py", line 1, in <module>
class Klass(object):
File "call_staticmethod.py", line 7, in Klass
_ANS = _stat_func()
TypeError: 'staticmethod' object is not callable
Entiendo por qué sucede esto (enlace de descriptor)y puede solucionarlo convirtiendo manualmente _stat_func()
en un método estático después de su último uso, así:
class Klass(object):
def _stat_func():
return 42
_ANS = _stat_func() # use the non-staticmethod version
_stat_func = staticmethod(_stat_func) # convert function to a static method
def method(self):
ret = Klass._stat_func() + Klass._ANS
return ret
Entonces mi pregunta es:
¿Hay formas más limpias o más “Pythonic” de lograr esto?
ben
actualización para la versión de python> = 3.10: las funciones de método estático se pueden llamar desde dentro del alcance de la clase muy bien (para obtener más información, consulte: rastreador de problemas de pythono “qué hay de nuevo”o aquí)
para la versión de python
staticmethod
los objetos aparentemente tienen un __func__
atributo que almacena la función sin procesar original (tiene sentido que tuvieran que hacerlo). Así que esto funcionará:
class Klass(object):
@staticmethod # use as decorator
def stat_func():
return 42
_ANS = stat_func.__func__() # call the staticmethod
def method(self):
ret = Klass.stat_func()
return ret
Aparte, aunque sospechaba que un objeto de método estático tenía algún tipo de atributo que almacenaba la función original, no tenía idea de los detalles. Con el espíritu de enseñarle a alguien a pescar en lugar de darle un pez, esto es lo que hice para investigar y descubrirlo (un C&P de mi sesión de Python):
>>> class Foo(object):
... @staticmethod
... def foo():
... return 3
... global z
... z = foo
>>> z
<staticmethod object at 0x0000000002E40558>
>>> Foo.foo
<function foo at 0x0000000002E3CBA8>
>>> dir(z)
['__class__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> z.__func__
<function foo at 0x0000000002E3CBA8>
Tipos similares de excavación en una sesión interactiva (dir
es muy útil) a menudo puede resolver este tipo de preguntas muy rápidamente.
-
Buena actualización… Estaba a punto de preguntar cómo sabía esto, ya que no lo veo en la documentación, lo que me pone un poco nervioso al usarlo porque podría ser un “detalle de implementación”.
– martineau
3 oct 2012 a las 23:31
-
@martineau No en este contexto. Los objetos de método enlazados y no enlazados tienen un
im_func
atributo para obtener la función en bruto, y el__func__
El atributo de estos objetos de método es el mismo queim_func
.staticmethod
los objetos no tienenim_func
atributo (como se muestra en midir
publicación anterior, y confirmado en una sesión de interpretación real).– ben
4 de octubre de 2012 a las 0:38
-
Ya veo, así que técnicamente no está documentado en este contexto.
– martineau
4 oct 2012 a las 0:56
-
@AkshayHazari El
__func__
El atributo de un método estático te da una referencia a la función original, exactamente como si nunca hubieras usado elstaticmethod
decorador. Entonces, si su función requiere argumentos, tendrá que pasarlos al llamar__func__
. El mensaje de error suena como si no le hubieras dado ningún argumento. Si elstat_func
en el ejemplo de esta publicación tomó dos argumentos, usaría_ANS = stat_func.__func__(arg1, arg2)
– ben
23 de enero de 2018 a las 5:48
-
@AkshayHazari No estoy seguro de entender. Pueden ser variables, solo tienen que ser variables dentro del alcance: definidas anteriormente en el mismo alcance donde se define la clase (a menudo global), o definidas anteriormente dentro del alcance de la clase (
stat_func
sí mismo es tal variable). ¿Quiso decir que no puede usar atributos de instancia de la clase que está definiendo? Eso es cierto, pero no sorprende; nosotros no tener una instancia de la clase, ¡ya que todavía estamos definiendo la clase! Pero de todos modos, solo los quise decir como sustitutos de cualquier argumento que quisieras pasar; podrías usar literales allí.– ben
23 de enero de 2018 a las 7:41
Jan Vorcak
Esta es la forma que prefiero:
class Klass(object):
@staticmethod
def stat_func():
return 42
_ANS = stat_func.__func__()
def method(self):
return self.__class__.stat_func() + self.__class__._ANS
Prefiero esta solución a Klass.stat_func
debido a la principio SECO. me recuerda a la razón por la cual hay una nueva super()
en pitón 3 🙂
Pero estoy de acuerdo con los demás, por lo general, la mejor opción es definir una función de nivel de módulo.
por ejemplo con @staticmethod
función, la recursividad podría no verse muy bien (Debería romper el principio DRY llamando Klass.stat_func
adentro Klass.stat_func
). Eso es porque no tienes referencia a self
dentro del método estático. Con la función de nivel de módulo, todo se verá bien.
-
Si bien estoy de acuerdo en que usar
self.__class__.stat_func()
en métodos regulares tiene ventajas (DRY y todo eso) sobre el usoKlass.stat_func()
ese no era realmente el tema de mi pregunta; de hecho, evité usar el primero para no nublar el problema de la inconsistencia.– martineau
10 de diciembre de 2014 a las 22:06
-
No es realmente un problema de DRY per se.
self.__class__
es mejor porque si una subclase anulastat_func
entoncesSubclass.method
llamará a la subclasestat_func
. Pero para ser completamente honesto, en esa situación es mucho mejor usar un método real en lugar de uno estático.– Asmeurer
12/04/2017 a las 19:44
-
@asmeurer: no puedo usar un método real porque aún no se han creado instancias de la clase; no pueden serlo porque la clase en sí ni siquiera se ha definido por completo todavía.
– martineau
6 de junio de 2018 a las 19:09
-
Quería una forma de llamar al método estático para mi clase (que se hereda, por lo que el padre en realidad tiene la función) y esto parece lo mejor con diferencia (y seguirá funcionando si lo anulo más adelante).
– blanquito04
24/10/2018 a las 20:46
keith
Esto se debe a que staticmethod es un descriptor y requiere una obtención de atributo de nivel de clase para ejecutar el protocolo del descriptor y obtener el verdadero invocable.
Desde el código fuente:
Se puede llamar en la clase (p. ej.
C.f()
) o en una instancia (p. ej.C().f()
); la instancia se ignora excepto por su clase.
Pero no directamente desde dentro de la clase mientras se define.
Pero como mencionó un comentarista, este no es realmente un diseño “Pythonic” en absoluto. Simplemente use una función de nivel de módulo en su lugar.
-
Como dije en mi pregunta, entiendo por qué el código original no funcionó. ¿Puede explicar (o proporcionar un enlace a algo que lo haga) por qué se considera no Pythonic?
– martineau
3 oct 2012 a las 23:48
-
El método estático requiere que el objeto de la clase funcione correctamente. Pero si lo llama desde dentro de la clase en el nivel de clase, la clase no está completamente definida en ese punto. Así que no puedes hacer referencia a él todavía. Está bien llamar al método estático desde fuera de la clase, después de definirlo. Pero “pitónico” en realidad no está bien definido, al igual que la estética. Puedo decirles que mi primera impresión de ese código no fue favorable.
– Keith
3 oct 2012 a las 23:58
-
¿Impresión de qué versión (o ambas)? ¿Y por qué, específicamente?
– martineau
4 de octubre de 2012 a las 0:11
-
No, lo que quiero hacer en realidad es bastante simple: eliminar un código común y reutilizarlo, tanto para crear un atributo de clase privada en el momento de la creación de la clase como más adelante dentro de uno o más métodos de clase. Este código común no tiene uso fuera de la clase, por lo que naturalmente quiero que sea parte de ella. Usar una metaclase (o un decorador de clase) funcionaría, pero parece una exageración para algo que debería ser fácil de hacer, en mi humilde opinión.
– martineau
4 oct 2012 a las 16:32
-
Eso está bien para los atributos de clase que son constantes triviales como en mi código de ejemplo simplificado, pero no tanto si no lo son y requieren algún cálculo.
– martineau
4 oct 2012 a las 16:50
¿Qué hay de inyectar el atributo de clase después de la definición de clase?
class Klass(object):
@staticmethod # use as decorator
def stat_func():
return 42
def method(self):
ret = Klass.stat_func()
return ret
Klass._ANS = Klass.stat_func() # inject the class attribute with static method value
schatten
¿Qué pasa con esta solución? No se basa en el conocimiento de @staticmethod
implementación del decorador. La clase interna StaticMethod actúa como un contenedor de funciones de inicialización estáticas.
class Klass(object):
class StaticMethod:
@staticmethod # use as decorator
def _stat_func():
return 42
_ANS = StaticMethod._stat_func() # call the staticmethod
def method(self):
ret = self.StaticMethod._stat_func() + Klass._ANS
return ret
-
+1 para creatividad, pero ya no me preocupa usar
__func__
porque ahora está documentado oficialmente (ver la sección Otros cambios de idioma de What’s New in Python 2.7 y su referencia a Edición 5982). Su solución es aún más portátil, ya que probablemente también funcionaría en versiones de Python anteriores a la 2.6 (cuando__func__
se introdujo por primera vez como sinónimo deim_func
).– martineau
16 de mayo de 2014 a las 1:12
-
Esta es la única solución que funciona con Python 2.6.
– Benselme
12 de julio de 2017 a las 21:46
-
@benselme: no puedo verificar su afirmación porque no tengo instalado Python 2.6, pero tengo serias dudas de que sea el único…
– martineau
6 de junio de 2018 a las 19:12
Si el “problema central” es asignar variables de clase usando funciones, una alternativa es usar una metaclase (es un poco “molesto” y “mágico” y estoy de acuerdo en que el método estático debería poder llamarse dentro de la clase, pero desafortunadamente no lo es t). De esta forma, podemos refactorizar el comportamiento en una función independiente y no abarrotar la clase.
class KlassMetaClass(type(object)):
@staticmethod
def _stat_func():
return 42
def __new__(cls, clsname, bases, attrs):
# Call the __new__ method from the Object metaclass
super_new = super().__new__(cls, clsname, bases, attrs)
# Modify class variable "_ANS"
super_new._ANS = cls._stat_func()
return super_new
class Klass(object, metaclass=KlassMetaClass):
"""
Class that will have class variables set pseudo-dynamically by the metaclass
"""
pass
print(Klass._ANS) # prints 42
Usar esta alternativa “en el mundo real” puede ser problemático. Tuve que usarlo para anular las variables de clase en las clases de Django, pero en otras circunstancias tal vez sea mejor optar por una de las alternativas de las otras respuestas.
-
+1 para creatividad, pero ya no me preocupa usar
__func__
porque ahora está documentado oficialmente (ver la sección Otros cambios de idioma de What’s New in Python 2.7 y su referencia a Edición 5982). Su solución es aún más portátil, ya que probablemente también funcionaría en versiones de Python anteriores a la 2.6 (cuando__func__
se introdujo por primera vez como sinónimo deim_func
).– martineau
16 de mayo de 2014 a las 1:12
-
Esta es la única solución que funciona con Python 2.6.
– Benselme
12 de julio de 2017 a las 21:46
-
@benselme: no puedo verificar su afirmación porque no tengo instalado Python 2.6, pero tengo serias dudas de que sea el único…
– martineau
6 de junio de 2018 a las 19:12
para la versión de python> = 3.10, las funciones de método estático se pueden llamar desde dentro del alcance de la clase muy bien
- Un consejo para el foro de python.org: https://discuss.python.org/t/staticmethod-called-outside-class-scope/15007/4 (para más información: sigue leyendo en Problema de Python #87848o “qué hay de nuevo”)
- probé en python 3.9 (rockylinux:9) y python 3.10 (fedora:36) con el siguiente ejemplo de código mínimo:
class Tmp:
@staticmethod
def func1():
return 1234
X = func1()
print(Tmp.X)
- mi prueba muestra:
- errores de python3.9
- python3.10 funciona bien (sin errores)
Si está preguntando sobre Pythonicity, entonces el consejo estándar es no usar
staticmethod
en absoluto. Por lo general, son más útiles como funciones a nivel de módulo, en cuyo caso su problema no es un problema.classmethod
por otro lado…– Benjamín Hodgson
3 oct 2012 a las 23:22
@poorsod: Sí, conozco esa alternativa. Sin embargo, en el código real donde encontré este problema, hacer que la función sea un método estático en lugar de ponerlo a nivel de módulo tiene más sentido que en el ejemplo simple que se usa en mi pregunta.
– martineau
3 oct 2012 a las 23:43
FYI en python 3.10 puede llamar a funciones de método estático muy bien desde el cuerpo de la clase. para obtener más información, consulte aquí: stackoverflow.com/a/75628150/52074
– Trevor Boyd Smith
ayer