255 votos

¿Para qué sirven los métodos de clase en Python?

Estoy aprendiendo Python y mi lección más reciente fue que Python no es Java y por eso me he pasado un rato convirtiendo todos los métodos de mi clase en funciones.

Ahora me doy cuenta de que no necesito usar métodos de clase para lo que haría con static en Java, pero ahora no estoy seguro de cuándo los utilizaría. Todos los consejos que puedo encontrar sobre los métodos de clase de Python van en la línea de que los novatos como yo deberían mantenerse alejados de ellos, y la documentación estándar es de lo más opaca cuando se habla de ellos.

¿Alguien tiene un buen ejemplo de uso de un método de clase en Python o, al menos, alguien puede decirme cuándo se pueden utilizar los métodos de clase con sentido?

181voto

John Millikin Puntos 86775

Los métodos de clase son para cuando se necesita tener métodos que no son específicos de una instancia en particular, pero que aún involucran a la clase de alguna manera. Lo más interesante de ellos es que pueden ser sobrescritos por subclases, algo que simplemente no es posible en los métodos estáticos de Java o en las funciones a nivel de módulo de Python.

Si tiene una clase MyClass y una función a nivel de módulo que opera sobre MyClass (fábrica, stub de inyección de dependencia, etc), lo convierten en un classmethod . Entonces estará disponible para las subclases.

64voto

AmanKow Puntos 732

Los métodos de fábrica (constructores alternativos) son, en efecto, un ejemplo clásico de métodos de clase.

Básicamente, los métodos de clase son adecuados siempre que se quiera tener un método que encaje de forma natural en el espacio de nombres de la clase, pero que no esté asociado a una instancia concreta de la misma.

Como ejemplo, en el excelente unipath módulo:

Directorio actual

  • Path.cwd()
    • Devuelve el directorio actual; por ejemplo Path("/tmp/my_temp_dir") . Este es un método de clase.
  • .chdir()
    • Hacer de sí mismo el directorio actual.

Como el directorio actual es un proceso amplio, el cwd no tiene ninguna instancia particular a la que deba asociarse. Sin embargo, al cambiar el cwd al directorio de un determinado Path debería ser un método de instancia.

Hmmm... como Path.cwd() devuelve efectivamente un Path instancia, supongo que podría considerarse un método de fábrica...

48voto

Brandon Rhodes Puntos 21188

Piénsalo así: los métodos normales son útiles para ocultar los detalles del envío: puedes escribir myobj.foo() sin preocuparse de si el foo() es implementado por el método myobj la clase del objeto o una de sus clases padre. Los métodos de clase son exactamente análogos a esto, pero con el objeto de clase en su lugar: permiten llamar a MyClass.foo() sin tener que preocuparse de si foo() es implementado especialmente por MyClass porque necesitaba su propia versión especializada, o si está dejando que su clase madre maneje la llamada.

Los métodos de clase son esenciales cuando se hace una configuración o un cálculo que precede a la creación de una instancia real, porque hasta que la instancia no existe, obviamente no se puede utilizar la instancia como punto de envío para sus llamadas a métodos. Un buen ejemplo puede verse en el código fuente de SQLAlchemy; echa un vistazo a la sección dbapi() en el siguiente enlace:

https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47

Puede ver que el dbapi() que un backend de base de datos utiliza para importar la biblioteca de base de datos específica del proveedor que necesita bajo demanda, es un método de clase porque necesita ejecutar antes de instancias de una determinada conexión a la base de datos comienzan a crearse - pero que no puede ser una simple función o una función estática, porque quieren que sea capaz de llamar a otros métodos de apoyo que podrían igualmente necesitar ser escritos más específicamente en las subclases que en su clase padre. Y si se despacha a una función o clase estática, entonces se "olvida" y se pierde el conocimiento sobre qué clase está haciendo la inicialización.

29voto

Marvo Puntos 5444

Recientemente quería una clase de registro muy ligera que emitiera cantidades variables de salida dependiendo del nivel de registro que se pudiera establecer mediante programación. Pero no quería instanciar la clase cada vez que quisiera dar salida a un mensaje de depuración o error o advertencia. Pero también quería encapsular el funcionamiento de esta instalación de registro y hacerla reutilizable sin la declaración de ningún global.

Así que utilicé variables de clase y el @classmethod decorador para conseguirlo.

Con mi simple clase de Registro, podría hacer lo siguiente:

Logger._level = Logger.DEBUG

Entonces, en mi código, si quería escupir un montón de información de depuración, simplemente tenía que codificar

Logger.debug( "this is some annoying message I only want to see while debugging" )

Los errores se pueden eliminar con

Logger.error( "Wow, something really awful happened." )

En el entorno de "producción", puedo especificar

Logger._level = Logger.ERROR

y ahora, sólo se emitirá el mensaje de error. El mensaje de depuración no se imprimirá.

Aquí está mi clase:

class Logger :
    ''' Handles logging of debugging and error messages. '''

    DEBUG = 5
    INFO  = 4
    WARN  = 3
    ERROR = 2
    FATAL = 1
    _level = DEBUG

    def __init__( self ) :
        Logger._level = Logger.DEBUG

    @classmethod
    def isLevel( cls, level ) :
        return cls._level >= level

    @classmethod
    def debug( cls, message ) :
        if cls.isLevel( Logger.DEBUG ) :
            print "DEBUG:  " + message

    @classmethod
    def info( cls, message ) :
        if cls.isLevel( Logger.INFO ) :
            print "INFO :  " + message

    @classmethod
    def warn( cls, message ) :
        if cls.isLevel( Logger.WARN ) :
            print "WARN :  " + message

    @classmethod
    def error( cls, message ) :
        if cls.isLevel( Logger.ERROR ) :
            print "ERROR:  " + message

    @classmethod
    def fatal( cls, message ) :
        if cls.isLevel( Logger.FATAL ) :
            print "FATAL:  " + message

Y algún código que lo pruebe un poco:

def logAll() :
    Logger.debug( "This is a Debug message." )
    Logger.info ( "This is a Info  message." )
    Logger.warn ( "This is a Warn  message." )
    Logger.error( "This is a Error message." )
    Logger.fatal( "This is a Fatal message." )

if __name__ == '__main__' :

    print "Should see all DEBUG and higher"
    Logger._level = Logger.DEBUG
    logAll()

    print "Should see all ERROR and higher"
    Logger._level = Logger.ERROR
    logAll()

26voto

Aaron Maenpaa Puntos 39173

Los constructores alternativos son el ejemplo clásico.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X