Pregunta

Estoy aprendiendo Python por mi cuenta y mi lección más reciente fue que Python no es Java, por lo que pasé 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 en Java, pero ahora no estoy seguro de cuándo los usaría.Todos los consejos que puedo encontrar sobre los métodos de la clase Python están en la línea de que los novatos como yo deberían mantenerse alejados de ellos, y la documentación estándar es más opaca cuando se analizan.

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

¿Fue útil?

Solución

Los métodos de clase son para cuando necesitas tener métodos que no sean específicos de ninguna instancia en particular, pero que aun así involucren 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 tienes una clase MyClass, y una función a nivel de módulo que opera en MyClass (fábrica, código auxiliar de inyección de dependencia, etc.), lo convierten en un classmethod.Entonces estará disponible para las subclases.

Otros consejos

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 desee tener un método que encaje naturalmente en el espacio de nombres de la clase, pero que no esté asociado con una instancia particular de la clase.

A modo de ejemplo, en el excelente unipath módulo:

Directorio actual

  • Path.cwd()
    • Devuelve el directorio actual real;p.ej., 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 cwd El método no tiene ninguna instancia particular con la que deba asociarse.Sin embargo, cambiar la cwd al directorio de un determinado Path De hecho, la instancia debería ser un método de instancia.

Mmm...como Path.cwd() de hecho devuelve un Path Por ejemplo, supongo que podría considerarse un método de fábrica...

Piensa en ello de esta manera:Los métodos normales son útiles para ocultar los detalles del envío:puedes escribir myobj.foo() sin preocuparse por si el foo() El método es implementado por el myobj clase 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:te dejan llamar MyClass.foo() sin tener que preocuparse por si foo() se implementa especialmente por MyClass porque necesitaba su propia versión especializada, o si está permitiendo que su clase principal maneje la llamada.

Los métodos de clase son esenciales cuando se realizan configuraciones o cálculos que precede la creación de una instancia real, porque hasta que la instancia exista obviamente no puede usarla como punto de envío para sus llamadas a métodos.Se puede ver un buen ejemplo en el código fuente de SQLAlchemy;echa un vistazo a la dbapi() método de clase en el siguiente enlace:

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

Puedes ver que el dbapi() El método, que utiliza el backend de una 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 necesita ejecutarse. antes Se comienzan a crear instancias de una conexión de base de datos particular, pero que no puede ser una función simple o estática, porque quieren que pueda llamar a otros métodos de soporte que de manera similar podrían necesitar escribirse más específicamente en subclases que en sus clases principales. clase.Y si 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.

Recientemente quería una clase de registro muy liviana que generara diferentes cantidades de resultados dependiendo del nivel de registro que pudiera establecerse mediante programación.Pero no quería crear una instancia de la clase cada vez que quisiera generar un mensaje de depuración, un error o una advertencia.Pero también quería resumir el funcionamiento de esta instalación de registro y hacerla reutilizable sin la declaración de ningún global.

Entonces usé variables de clase y el @classmethod decorador 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 mostrar 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 solucionarse con

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

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

Logger._level = Logger.ERROR

y ahora, solo se generará 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 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()

Los constructores alternativos son el ejemplo clásico.

Creo que la respuesta más clara es AmanKow's uno.Todo se reduce a cómo quieres organizar tu código.Puede escribir todo como funciones a nivel de módulo que están incluidas 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ápidamente.

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

Con las funciones de clase obtienes otro nivel de división en tu código en comparación con las funciones a nivel de módulo.Por lo tanto, puede agrupar funciones relacionadas dentro de una clase para hacerlas más específicas de una tarea que asignó a esa clase.Por ejemplo, puedes 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 funciones y es más obvio lo que hace cada función.También evita conflictos de nombres, por ejemplo, la función copiar 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 utilizar uno al lado del otro.

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 (p. ej.Es posible que un usuario administrador desee 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 capturar 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 encontré un uso para el método estático o el método de clase.Todavía tengo que ver una operación que no se pueda realizar 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.

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 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 tiene muy pocas palabras, por lo que no ayudará a comprender esa característica.Estuve buscando y buscando en Google y encontré la respuesta -> http://code.anjanesh.net/2007/12/python-classmethods.html.

Si te da pereza hacer clic en él.Mi explicación es más corta y a continuación.:)

en PHP (tal vez no todos conozcan PHP, pero este lenguaje es tan sencillo que todos deberían entender de qué 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 resultados 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á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 utilizando funciones a nivel de módulo, por supuesto...

Lo que me acaba de sorprender, viniendo de Ruby, es que lo que se llama clase método y el llamado instancia El método es solo una función con significado semántico aplicado a su primer parámetro, que se pasa silenciosamente cuando el función se llama como un método de un objeto (es decir. obj.meth()).

Normalmente eso objeto debe ser una instancia pero el @classmethod decorador de métodos Cambia las reglas para aprobar una clase.Puedes llamar a un método de clase en una instancia (es solo una función); el primer argumento será su clase.

Porque es solo un función, solo se puede declarar una vez en un ámbito determinado (es decir, class definición).Por lo tanto, se sigue, como sorpresa para un rubista, que no puedes 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 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 agrega @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

Es la única convención la que dicta que usemos self para ese primer argumento en un método de instancia y cls en un método de clase.No usé ninguno de los dos aquí para ilustrar que es solo un argumento.En Rubí, self es un palabra clave.

Contraste con Rubí:

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 la clase Python es solo un función decorada y puedes usar las mismas técnicas para crea 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 aún accesible.


nota: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 es incorrecto.

Este es un tema interesante.Mi opinión es que el método de clase de 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 es 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 el patrón de fábrica según tengo entendido.Esto probablemente sea 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

Me hice la misma pregunta varias veces.Y aunque los chicos aquí se esforzaron por 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 se hace referencia al método estático.Y en caso de que alguien ya conozca los métodos de instancia (lo cual supongo), esta respuesta podría ser la pieza final para armarlo todo...

También se puede encontrar una elaboración más detallada sobre este tema en la documentación:La jerarquía de tipos estándar (desplácese hacia abajo hasta Métodos de instancia sección)

Por supuesto, una clase define un conjunto de instancias.Y los métodos de una clase funcionan en las instancias individuales.Los métodos de clase (y 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 estudiantes, es posible que desee variables de clase o métodos que definan cosas como el conjunto de calificaciones del que pueden ser miembros los estudiantes.

También puede utilizar 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.Estudiantes.all_of_em(grado='juniors')

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top