¿Cuál es el propósito de los métodos de clase?


Me estoy enseñando Python y mi lección más reciente fue que Python no es Java, por lo que he pasado un tiempo convirtiendo todos mis métodos de Clase en funciones.

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

¿Alguien tiene un buen ejemplo de usar un método de Clase en Python o al menos alguien puede decirme cuándo se pueden usar métodos de clase con sensatez?

Author: martineau, 2008-09-01

15 answers

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

Si tiene una clase MyClass, y una función de nivel de módulo que opera en MyClass (fábrica, stub de inyección de dependencias, etc.), hágalo un classmethod. Entonces será disponible para subclases.

 165
Author: John Millikin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-01 18:45:56

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

Básicamente, los métodos de clase son adecuados en cualquier momento que desee tener un método que se ajuste naturalmente al espacio de nombres de la clase, pero no esté asociado con una instancia particular de la clase.

Como ejemplo, en el excelente módulo unipath:

Directorio actual

  • Path.cwd()
    • Devuelve el directorio actual; por ejemplo, Path("/tmp/my_temp_dir"). Este es un método de clase.
  • .chdir()
    • Haga self el directorio actual.

Como el directorio actual es ancho de proceso, el método cwd no tiene ninguna instancia particular con la que deba estar asociado. Sin embargo, cambiar el cwd al directorio de una instancia dada Path debería ser un método de instancia.

Hmmm... como Path.cwd() devuelve una instancia Path, supongo que podría considerarse un método factory...

 62
Author: Wayne Werner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-23 06:19:35

Piénselo de esta manera: los métodos normales son útiles para ocultar los detalles del envío: puede escribir myobj.foo() sin preocuparse de si el método foo() está implementado por la clase del objeto myobj 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: le permiten llamar a MyClass.foo() sin tener que preocuparse si foo() está implementado especialmente por MyClass porque necesita su propia versión especializada, o si está dejando que su padre clase manejar la llamada.

Los métodos de clase son esenciales cuando se está haciendo una configuración o cálculo que precede a la creación de una instancia real, porque hasta que la instancia exista, obviamente no se puede usar la instancia como punto de despacho para las llamadas a los métodos. Un buen ejemplo se puede ver en el código fuente de SQLAlchemy; eche un vistazo al método de clase dbapi() en lo siguiente enlace:

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

Puede ver que el método 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 que se empiecen a crear instancias de una conexión de base de datos en particular , pero quieren que sea capaz de llamar a otros métodos de soporte que de manera similar podrían necesitar ser escritos más específicamente en subclases que en su clase padre. Y si envías a una función o clase estática, entonces "olvidas" y pierdes el conocimiento sobre qué clase está haciendo la inicialización.

 45
Author: Brandon Rhodes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-29 16:46:00

Recientemente quería una clase de registro muy liviana que generara cantidades variables de salida dependiendo del nivel de registro que se pudiera establecer programáticamente. Pero no quería crear una instancia de la clase cada vez que quería generar un mensaje de depuración o error o advertencia. Pero también quería encapsular el funcionamiento de esta instalación maderera y hacerla reutilizable sin la declaración de ningún global.

Así que usé variables de clase y el decorador @classmethod para lograr este.

Con mi clase de Registro simple, 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 podrían eliminarse con

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

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

Logger._level = Logger.ERROR

Y ahora, solo se mostrará 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 prueba bit:

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()
 27
Author: Marvo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-11 01:48:33

Los constructores alternativos son el ejemplo clásico.

 23
Author: Aaron Maenpaa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-01 18:27:27

Creo que la respuesta más clara es la de AmanKow. Se reduce a cómo u desea organizar su código. Puede escribir todo como funciones de nivel de módulo que están envueltas en el espacio de nombres del módulo, es decir,

module.py (file 1)
---------
def f1() : pass
def f2() : pass
def f3() : pass


usage.py (file 2)
--------
from module import *
f1()
f2()
f3()
def f4():pass 
def f5():pass

usage1.py (file 3)
-------------------
from usage import f4,f5
f4()
f5()

El código de procedimiento anterior no está bien organizado, como puede ver después de solo 3 módulos se vuelve confuso, ¿qué es lo que hace cada método ? Puede usar nombres descriptivos largos para funciones(como en Java) pero aún así su código se vuelve inmanejable muy rápido.

El la forma orientada a objetos es descomponer su código en bloques manejables, es decir, clases y objetos y funciones que se pueden asociar con instancias de objetos o con clases.

Con las funciones de clase se obtiene otro nivel de división en el código en comparación con las funciones de nivel de módulo. Por lo tanto, puede agrupar funciones relacionadas dentro de una clase para hacerlas más específicas para una tarea que asignó a esa clase. Por ejemplo, puede crear una clase de utilidad de archivo:

class FileUtil ():
  def copy(source,dest):pass
  def move(source,dest):pass
  def copyDir(source,dest):pass
  def moveDir(source,dest):pass

//usage
FileUtil.copy("1.txt","2.txt")
FileUtil.moveDir("dir1","dir2")

De esta manera es más flexible y más mantenible, agrupar funciones juntas y es más obvio lo que hace cada función. También puede evitar conflictos de nombres, por ejemplo, la copia de función puede existir en otro módulo importado (por ejemplo, copia de red) que use en su código, por lo que cuando use el nombre completo FileUtil.copy() elimina el problema y ambas funciones de copia se pueden usar una al lado de la otra.

 10
Author: firephil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-04 23:26:34

Cuando un usuario inicia sesión en mi sitio web, se crea una instancia de un objeto User() a partir del nombre de usuario y la contraseña.

Si necesito un objeto user sin que el usuario esté allí para iniciar sesión (por ejemplo, un usuario administrador puede querer eliminar otra cuenta de usuario, por lo que necesito crear una instancia de ese usuario y llamar a su método de eliminación):

Tengo métodos de clase para agarrar el objeto user.

class User():
    #lots of code
    #...
    # more code

    @classmethod
    def get_by_username(cls, username):
        return cls.query(cls.username == username).get()

    @classmethod
    def get_by_auth_id(cls, auth_id):
        return cls.query(cls.auth_id == auth_id).get()
 9
Author: robert king,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-26 06:36:52

Le permite escribir métodos de clase genéricos que puede usar con cualquier clase compatible.

Por ejemplo:

@classmethod
def get_name(cls):
    print cls.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

Si no usas @classmethod puedes hacerlo con la palabra clave self pero necesita una instancia de Clase:

def get_name(self):
    print self.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C().get_name() #<-note the its an instance of class C
 6
Author: yet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-02 08:47:32

Honestamente? Nunca he encontrado un uso para staticmethod o classmethod. Todavía tengo que ver una operación que no se puede hacer usando una función global o un método de instancia.

Sería diferente si python usara miembros privados y protegidos más como Java. En Java, necesito un método estático para poder acceder a los miembros privados de una instancia para hacer cosas. En Python, eso rara vez es necesario.

Por lo general, veo a la gente usando métodos estáticos y métodos de clase cuando todo lo que realmente necesitan lo que hay que hacer es usar mejor los espacios de nombres a nivel de módulo de Python.

 5
Author: Jason Baker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-01 18:54:29

Solía trabajar con PHP y recientemente me preguntaba, ¿qué está pasando con este método de clase? El manual de Python es muy técnico y muy corto en palabras, por lo que no ayudará a comprender esa característica. Estaba googleando y googleando y encontré respuesta - > http://code.anjanesh.net/2007/12/python-classmethods.html .

Si eres perezoso para hacer clic en él. Mi explicación es más corta y más abajo. :)

En PHP (tal vez no todos conocen PHP, pero este lenguaje es muy sencillo que todo el mundo debería entender de lo que estoy hablando) tenemos variables estáticas como esta:


class A
{

    static protected $inner_var = null;

    static public function echoInnerVar()
    {
        echo self::$inner_var."\n";
    }

    static public function setInnerVar($v)
    {
        self::$inner_var = $v;
    }

}

class B extends A
{
}

A::setInnerVar(10);
B::setInnerVar(20);

A::echoInnerVar();
B::echoInnerVar();

La salida será en ambos casos 20.

Sin embargo en python podemos añadir @classmethod decorator y así es posible tener salida 10 y 20 respectivamente. Ejemplo:


class A(object):
    inner_var = 0

    @classmethod
    def setInnerVar(cls, value):
        cls.inner_var = value

    @classmethod
    def echoInnerVar(cls):
        print cls.inner_var


class B(A):
    pass


A.setInnerVar(10)
B.setInnerVar(20)

A.echoInnerVar()
B.echoInnerVar()
Inteligente, ¿no?
 5
Author: Drachenfels,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-05 16:48:51

Los métodos de clase proporcionan un "azúcar semántica" (no se sabe si este término es ampliamente utilizado) - o "conveniencia semántica".

Ejemplo: tienes un conjunto de clases que representan objetos. Es posible que desee tener el método de clase all() o find() para escribir User.all() o User.find(firstname='Guido'). Eso podría hacerse usando funciones a nivel de módulo, por supuesto...

 4
Author: Pierre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-02 09:41:08

Este es un tema interesante. Mi opinión es que python classmethod opera como un singleton en lugar de una fábrica (que devuelve una instancia producida de una clase). La razón por la que es un singleton es que hay un objeto común que se produce (el diccionario) pero solo una vez para la clase pero compartido por todas las instancias.

Para ilustrar esto aquí hay un ejemplo. Tenga en cuenta que todas las instancias tienen una referencia al diccionario único. Este no es un patrón de fábrica como yo lo entiendo. Esto es probablemente muy exclusivo de python.

class M():
 @classmethod
 def m(cls, arg):
     print "arg was",  getattr(cls, "arg" , None),
     cls.arg = arg
     print "arg is" , cls.arg

 M.m(1)   # prints arg was None arg is 1
 M.m(2)   # prints arg was 1 arg is 2
 m1 = M()
 m2 = M() 
 m1.m(3)  # prints arg was 2 arg is 3  
 m2.m(4)  # prints arg was 3 arg is 4 << this breaks the factory pattern theory.
 M.m(5)   # prints arg was 4 arg is 5
 2
Author: Peter Moore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-07 05:06:16

Lo que me golpeó, viniendo de Ruby, es que un método llamado class y un método llamado instance es solo una función con significado semántico aplicado a su primer parámetro, que se pasa silenciosamente cuando se llama a la función como método de un objeto (es decir, obj.meth()).

Normalmente ese objeto debe ser una instancia pero el @classmethod método decorator cambia las reglas para pasar una clase. Puede llamar a un método de clase en una instancia (es solo una función) - el primer argyment será su clase.

Debido a que es solo una función , solo se puede declarar una vez en cualquier ámbito dado (es decir, definición class). Si sigue por lo tanto, como una sorpresa para un Rubyist, que no puede tener un método de clase y un método de instancia con el mismo nombre.

Considere esto:

class Foo():
  def foo(x):
    print(x)

Puede llamar a foo en una instancia

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>

, Pero no en una clase:

Foo.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

Ahora añadir @classmethod:

class Foo():
  @classmethod
  def foo(x):
    print(x)

Llamar a una instancia ahora pasa su clase:

Foo().foo()
__main__.Foo

Al igual que llamar a una clase:

Foo.foo()
__main__.Foo

Solo la convención dicta que usemos self para ese primer argumento en un método de instancia y cls en un método de clase. Usé ninguno de los dos aquí para ilustrar que es solo un argumento. En Ruby, self es una palabra clave.

Contraste con Ruby:

class Foo
  def foo()
    puts "instance method #{self}"
  end
  def self.foo()
    puts "class method #{self}"
  end
end

Foo.foo()
class method Foo

Foo.new.foo()
instance method #<Foo:0x000000020fe018>

El método de clase Python es solo una función decorada y puedes usar las mismas técnicas para crear tus propios decoradores. Un método decorado envuelve el método real (en el caso de @classmethod pasa el argumento de clase adicional). El método subyacente sigue ahí, oculto pero todavía accesible.


nota al pie: Escribí esto después de que un choque de nombres entre una clase y un método de instancia despertara mi curiosidad. Estoy lejos de ser un experto en Python y me gustaría recibir comentarios si algo de esto está mal.

 2
Author: starfry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-24 20:45:09

Me estaba haciendo la misma pregunta varias veces. Y a pesar de que los chicos aquí se esforzaron por explicarlo, en mi humilde opinión la mejor respuesta (y más simple) que he encontrado es la descripción del método de Clase en la Documentación de Python.

También se hace referencia al método Estático. Y en caso de que alguien ya conozca los métodos de instancia (que asumo), esta respuesta podría ser la pieza final para ponerlo todo junto...

Una elaboración más profunda sobre este tema puede ser se encuentra también en la documentación: La jerarquía de tipos estándar (desplácese hacia abajo hasta la sección Métodos de instancia)

 1
Author: Dee'Kej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-14 07:13:35

Una clase define un conjunto de instancias, por supuesto. Y los métodos de una clase funcionan en las instancias individuales. La clase methods (and variables) un lugar para colgar otra información que está relacionada con el conjunto de instancias sobre todo.

Por ejemplo, si su clase define un conjunto de estudiantes, es posible que desee variables de clase o métodos que definan cosas como el conjunto de calificaciones del que los estudiantes pueden ser miembros.

También puede usar métodos de clase para definir herramientas para trabajar en todo el set. Por ejemplo Estudiante.all_of_em () podría devolver todos los estudiantes conocidos. Obviamente, si su conjunto de instancias tiene más estructura que solo un conjunto, puede proporcionar métodos de clase para conocer esa estructura. Estudiante.all_of_em (grade='juniors')

Técnicas como esta tienden a llevar a almacenar miembros del conjunto de instancias en estructuras de datos que están arraigadas en variables de clase. Es necesario tener cuidado para evitar frustrar la recolección de basura a continuación.

 0
Author: Ben Hyde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-08 01:29:19