Versión de Python

2 minutos de lectura

avatar de usuario de martineau
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?

  • 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. classmethodpor 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

Avatar de usuario de Ben
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 que im_func. staticmethod los objetos no tienen im_func atributo (como se muestra en mi dir 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 el staticmethod 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 el stat_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

Avatar de usuario de Jan Vorcak
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_funcdebido 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 uso Klass.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 anula stat_funcentonces Subclass.method llamará a la subclase stat_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

Avatar de usuario de Keith
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

avatar de usuario de schatten
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 de im_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 de im_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

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)

¿Ha sido útil esta solución?