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


250

Me estoy enseñando 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 Class en funciones.

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

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

Respuestas:


178

Los métodos de clase son para cuando 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 anulados por subclases, algo que simplemente no es posible en los métodos estáticos de Java o las funciones de nivel de módulo de Python.

Si tiene una clase MyClassy una función de nivel de módulo que opera en MyClass (fábrica, código auxiliar de inyección de dependencia, etc.), hágalo a classmethod. Entonces estará disponible para las subclases.


Veo. Pero, ¿qué tal si solo quiero categorizar un método, que no necesita MyClass, pero que de alguna manera tiene sentido si me agrupo en otro MyClass? Aunque puedo pensar en moverlo al módulo de ayuda ..
swdev

Tengo una pregunta relacionada con la original, ¿tal vez podrías responderla al mismo tiempo? ¿Qué forma de llamar a los métodos de clase de un objeto es "mejor" o "más idiomático": obj.cls_mthd(...)o type(obj).cls_mthd(...)?
Alexey

63

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 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()
    • Conviértase en el directorio actual.

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

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


1
Sí ... los métodos de fábrica fueron lo primero que se me ocurrió. También en ese sentido hay cosas que implementarían un patrón Singleton, como: getAndOptionallyCreateSomeSingleInstanceOfSomeResource ()
Jemenake

3
En python, esos son métodos estáticos, no métodos de clase.
Jaykul

46

Piénselo de esta manera: los métodos normales son útiles para ocultar los detalles del despacho: puede escribir myobj.foo()sin preocuparse de si el foo()método es implementado por la myobjclase del objeto o una de sus clases principales. Los métodos de clase son exactamente análogos a esto, pero con el objeto de clase en su lugar: le permiten llamar MyClass.foo()sin tener que preocuparse de si foo()se implementa especialmente MyClassporque necesitaba su propia versión especializada, o si está permitiendo 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 la instancia exista, obviamente no puede usar la instancia como punto de envío para sus llamadas a métodos. Un buen ejemplo se puede ver en el código fuente de SQLAlchemy; Eche un vistazo al dbapi()método de clase en el siguiente enlace:

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

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


Enlace roto, por favor ajuste
piertoni

28

Hace poco quería una clase de registro muy liviana que produjera cantidades variables de salida dependiendo del nivel de registro que podría establecerse mediante programación. 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 de registro y hacerla reutilizable sin la declaración de ningún global.

Así que usé variables de clase y el @classmethoddecorador 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 pueden 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 saldrá 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 un código que lo prueba solo 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()

10
Esto no me parece que un buen ejemplo de método de clase sea bueno porque podría haberse hecho de una manera menos compleja simplemente haciendo que todos los métodos de clase funcionen a nivel de módulo y eliminando la clase por completo.
Martineau

10
Oh, aún más importante, Python proporciona una clase de registro muy simple de usar. Tan peor que crear una solución menos que óptima, reinventé la rueda. Doble bofetada. Pero proporciona un ejemplo de trabajo, al menos. Sin embargo, no estoy seguro de estar de acuerdo con deshacerme de la clase en un nivel conceptual. A veces, desea encapsular el código para una fácil reutilización, y los métodos de registro son un gran objetivo para la reutilización.
Marvo

3
¿Por qué no usarías un método estático?
bdforbes


11

Cuando un usuario inicia sesión en mi sitio web, se crea una instancia de un objeto Usuario () a partir del 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 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 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()

9

Creo que la respuesta más clara es la de AmanKow . Se reduce a cómo 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é 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.

La forma orientada a objetos es dividir su código en bloques manejables, es decir, las clases, 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 a 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 juntas y es más obvio para 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 usa en su código, por lo que cuando usa el nombre completo FileUtil.copy () elimina el problema y ambas funciones de copia se puede usar uno al lado del otro.


1
Para usar f1 (), f2 () y así sucesivamente, como lo hiciste, ¿no deberías usar desde el módulo de importación * y desde el uso de importación *?
Jblasco

@Jblasco Corrigí las declaraciones de importación para eliminar la confusión, sí, si importas el módulo, debes prefijar las funciones con el nombre del módulo. es decir, módulo de importación -> module.f1 () etc.
firephil

No estoy de acuerdo con esta respuesta; Python no es Java. El problema del código inmanejable aparentemente solo surge debido a la elección deliberada de nombres pobres en el ejemplo. Si, en cambio, replicara los FileUtilmétodos de clase como funciones de un file_utilmódulo, sería comparable y no abusaría de OOP cuando no existan objetos (en realidad, podría argumentar que es preferible, porque no termina con from file_util import FileUtilu otra verbosidad). Los conflictos de nombres se pueden evitar de manera similar en el código de procedimiento haciendo en import file_utillugar de hacerlo from file_util import ....
ForgottenUmbrella

7

¿Honestamente? Nunca he encontrado un uso para el método estático o método de clase. 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 lo hace 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 personas que usan métodos estáticos y métodos de clase cuando todo lo que realmente necesitan hacer es usar mejor los espacios de nombres a nivel de módulo de Python.


1
Privado: _variable_name y Protegido: __variable_name
Bradley Kreider

1
Uno de los usos indispensables es de unittest setUpClass y tearDownClass. Estás utilizando pruebas unitarias, ¿verdad? :)
dbn

7

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 lo 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

5

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

Si eres flojo, haz 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 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 el decorador @classmethod y, por lo tanto, es posible tener la 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?


Un problema potencial con su ejemplo de Python es que si B.setInnerVar(20)se 10omitió , se imprimiría dos veces (en lugar de dar un error en la segunda llamada echoInnerBar () que no inner_varse definió.
Martineau

5

Los métodos de clase proporcionan un "azúcar semántico" (no sé si este término se usa ampliamente) 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()escribir User.all()o User.find(firstname='Guido'). Eso podría hacerse usando funciones de nivel de módulo, por supuesto ...


Su ejemplo supone, por supuesto, que la clase realiza un seguimiento de todas sus instancias y puede acceder a ellas después de que se crean, algo que no se hace automáticamente.
Martineau

1
"azúcar semántico" suena como una buena combinación con "azúcar sintáctico".
XTL

3

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 es solo una función con significado semántico aplicado a su primer parámetro, que se pasa silenciosamente cuando la función se llama como método de un objeto (es decir obj.meth()).

Normalmente, ese objeto debe ser una instancia, pero el @classmethod decorador de métodos 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 (es decir, classdefinición). Si sigue, por lo tanto, como sorpresa para un Rubyist, que 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 fooen 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 agregue @classmethod:

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

Llamar a una instancia ahora pasa su clase:

Foo().foo()
__main__.Foo

como lo hace llamar a una clase:

Foo.foo()
__main__.Foo

Es solo la convención la que dicta que usamos selfpara ese primer argumento en un método de instancia y clsen un método de clase. No utilicé ninguno aquí para ilustrar que es solo un argumento. En Ruby, selfes 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 puede usar las mismas técnicas para crear sus propios decoradores . Un método decorado envuelve el método real (en el caso de @classmethodque pase el argumento de clase adicional). El método subyacente sigue ahí, oculto pero aún accesible .


nota al pie: escribí esto después de un choque 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.


"que se pasa silenciosamente cuando se llama a la función como método de un objeto" - creo que esto no es exacto. AFICT, nada se "pasa silenciosamente" en Python. En mi opinión, Python es mucho más sensible que Ruby en este sentido (a excepción de super()). AFICT, la "magia" ocurre cuando se establece o se lee un atributo. La llamada obj.meth(arg)en Python (a diferencia de Ruby) significa simplemente (obj.meth)(arg). Nada se pasa silenciosamente a ningún lado, obj.methes solo un objeto invocable que acepta un argumento menos que la función desde la que se creó.
Alexey

obj.meth, a su vez, significa simplemente getattr(obj, "meth").
Alexey

Tiene sentido tener una respuesta adaptada para personas que provienen de otros idiomas comunes. Sin embargo, una cosa que falta en esta conversación, que la une, es la noción de un descriptor de Python . Las funciones son descriptores , y así es como el primer argumento "automágicamente" pasa una instancia cuando una función es un atributo de la clase. También es cómo se implementan los métodos de clase, y se proporciona una implementación pura de Python en el CÓMO vinculado. El hecho de que también sea un decorador es algo auxiliar
juanpa.arrivillaga

2

Este es un tema interesante. Mi opinión sobre esto es que el método de clase python funciona 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í es un ejemplo. Tenga en cuenta que todas las instancias tienen una referencia al diccionario único. Este no es el patrón de fábrica, según tengo entendido. 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

Me hacía la misma pregunta varias veces. Y a pesar de que los chicos aquí trataron de explicarlo, en mi humilde opinión, la mejor respuesta (y la más simple) que he encontrado es la descripción del método Class 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 instancia (que supongo), esta respuesta podría ser la pieza final para poner todo junto ...

Se puede encontrar más y más detalles sobre este tema 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

@classmethodpuede ser útil para instanciar fácilmente objetos de esa clase de recursos externos. Considera lo siguiente:

import settings

class SomeClass:
    @classmethod
    def from_settings(cls):
        return cls(settings=settings)

    def __init__(self, settings=None):
        if settings is not None:
            self.x = settings['x']
            self.y = settings['y']

Luego en otro archivo:

from some_package import SomeClass

inst = SomeClass.from_settings()

Acceder a inst.x dará el mismo valor que la configuración ['x'].


0

Una clase define un conjunto de instancias, por supuesto. Y los métodos de una clase funcionan 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 sobre todo.

Por ejemplo, si su clase define un conjunto de estudiantes, es posible que desee variables o métodos de clase que definan cosas como el conjunto de calificaciones de las que los estudiantes 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 () 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. Students.all_of_em (grade = 'juniors')

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. Debe tener cuidado para evitar frustrar la recolección de basura.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.