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

Me estoy enseñando a Python y mi lección más reciente fue que Python no es Java , por lo que acabo de pasar 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 static métodos static en Java, pero ahora no estoy seguro de cuándo los usaría. Todos los consejos que puedo encontrar acerca de los métodos de la Clase Python están en línea con los novatos como yo, deberían evitarlos, y la documentación estándar es más opaca cuando se los discute.

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

Los métodos de clase son para cuando necesita tener métodos que no sean específicos de una instancia en particular, pero que involucren a la clase de alguna manera. Lo más interesante de ellos es que pueden ser reemplazados por subclases, algo que simplemente no es posible en los métodos estáticos de Java o en las funciones de 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, talón de dependency injection, etc.), classmethod un classmethod . Entonces estará disponible para las subclases.

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 en que le gustaría tener un método que naturalmente se adapte al espacio de nombres de la clase, pero que 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 actual; por ejemplo, Path("/tmp/my_temp_dir") . Este es un método de clase.
  • .chdir()
    • Hazte el directorio actual.

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

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

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() es implementado por la myobj objeto myobj o una de sus clases primarias. 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 de si foo() está implementado especialmente por MyClass porque necesitaba su propia versión especializada, o si es dejando que su clase padre maneje la llamada.

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

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

Puede ver que el método dbapi() , que utiliza un backend de base de datos para importar la biblioteca de base de datos específica del proveedor que necesita bajo demanda, es un método de clase porque debe ejecutarse antes de que se creen las instancias de una conexión de base de datos en particular, pero que no puede ser una función simple o una función estática, porque quieren que sea capaz de llamar a otros métodos de soporte que podrían necesitar escribirse más específicamente en subclases que en su clase principal. Y si envía a una función o clase estática, entonces “olvida” y pierde el conocimiento sobre qué clase está realizando la inicialización.

Hace poco quería una clase de registro muy liviana que pudiera generar cantidades variables de salida dependiendo del nivel de registro que se pudiera establecer mediante progtwigción. Pero no quería crear una instancia de la clase cada vez que quería enviar un mensaje de depuración o un error o advertencia. Pero también quería encapsular el funcionamiento de esta instalación de registro y hacerlo reutilizable sin la statement de ningún global.

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

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

 Logger._level = Logger.DEBUG 

Luego, 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 ser eliminados 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 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() 

Constructores alternativos son el ejemplo clásico.

Creo que la respuesta más clara es la de AmanKow . Se reduce a cómo quieres organizar tu código. Puede escribir todo como funciones de nivel de módulo que se envuelven 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 confunde, ¿qué hace cada método? Puede usar nombres largos y descriptivos para funciones (como en java) pero aún así su código se vuelve inmanejable muy rápido.

La forma orientada a objetos es dividir su código en bloques manejables, es decir, las clases y los objetos y las funciones pueden asociarse con instancias de objetos o con clases.

Con las funciones de clase, obtiene otro nivel de división en su 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 fácil de mantener, agrupa las funciones y es más obvio lo que hace cada función. También evita conflictos de nombres, por ejemplo, la copia de la función puede existir en otro módulo importado (por ejemplo, copia de red) que utiliza en su código, por lo que cuando usa el nombre completo FileUtil.copy (), elimina el problema y ambas funciones de copia Se pueden usar lado a lado.

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

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

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

 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() 

¿Honestamente? Nunca he encontrado un uso para el método static o classmethod. Todavía no he visto una operación que no se pueda hacer usando una función global o un método de instancia.

Sería diferente si Python utilizara 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 que la gente usa métodos estáticos y métodos de clase cuando lo único que realmente necesitan hacer es usar mejor los espacios de nombres de nivel de módulo de Python.

Le permite escribir métodos de clase generics 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 usa @classmethod , puede hacerlo con la palabra clave self pero necesita una instancia de Class:

 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 

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 entender esa característica. Estaba buscando en Google y encontré la respuesta -> http://code.anjanesh.net/2007/12/python-classmethods.html .

Si eres perezoso haz clic en él. Mi explicación es más corta y más abajo. 🙂

en PHP (tal vez no todos ustedes saben PHP, pero este lenguaje es tan sencillo que todos deberían 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 agregar @classmethod decorator y, por lo tanto, es posible tener una 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?

Los métodos de clase proporcionan un “azúcar semántica” (no sé 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 de nivel de módulo, por supuesto …

Lo que me llamó la atención , proveniente de Ruby, es que el llamado método de clase y el llamado método de instancia son 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 un método de un objeto (es decir, obj.meth() ).

Normalmente ese objeto debe ser una instancia, pero el decorador del método @classmethod 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 argumento será su clase.

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

Considera esto:

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

Puedes 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 "", line 1, in  TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead) 

Ahora agregue @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. No utilicé ni aquí para ilustrar que es solo un argumento. En Ruby, el 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 # 

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 conflicto de nombres entre una clase y un método de instancia despertó mi curiosidad. Estoy lejos de ser un experto en Python y me gustaría recibir comentarios si algo de esto está mal.

Este es un tema interesante. Mi opinión es que Python classmethod funciona como un singleton en lugar de una fábrica (que devuelve una instancia de una clase producida). 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. Esto no es un patrón de fábrica como 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 Mm(1) # prints arg was None arg is 1 Mm(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. Mm(5) # prints arg was 4 arg is 5 

Me hacía la misma pregunta pocas veces. Y a pesar de que los chicos aquí intentaron explicarlo, en mi humilde opinión, la mejor respuesta (y la más sencilla) que he encontrado es la descripción del método de clase en la documentación de Python.

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

Puede encontrar más detalles sobre este tema en la documentación: La jerarquía de tipos estándar (desplácese hasta la sección Métodos de instancia )

Una clase define un conjunto de instancias, por supuesto. Y los métodos de un trabajo de clase en las instancias individuales. Los métodos de clase (y las variables) son un lugar para colgar otra información relacionada con el conjunto de instancias en general.

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

También puede usar métodos de clase para definir herramientas para trabajar en todo el conjunto. Por ejemplo, Student.all_of_em () puede devolver todos los alumnos 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. Students.all_of_em (grado = ‘juniors’)

Las técnicas como esta tienden a conducir a almacenar miembros del conjunto de instancias en estructuras de datos que están enraizadas en variables de clase. Tienes que tener cuidado para evitar frustrar la recolección de basura entonces.