Significado de @classmethod y @staticmethod para principiantes [duplicado]

Resuelto asked hace 12 años • 12 respuestas

¿Qué significan @classmethody @staticmethodsignifican en Python y en qué se diferencian? ¿Cuándo debo usarlos, por qué debo usarlos y cómo debo usarlos?

Hasta donde tengo entendido, @classmethodle dice a una clase que es un método que debe heredarse en subclases, o... algo así. Sin embargo, ¿cuál es el punto de eso? ¿Por qué no simplemente definir el método de clase sin agregar @classmethodninguna @staticmethoddefinición @?

 avatar Aug 29 '12 20:08
Aceptado

Aunque classmethody staticmethodson bastante similares, hay una ligera diferencia en el uso de ambas entidades: classmethoddebe tener una referencia a un objeto de clase como primer parámetro, mientras que staticmethodno puede tener ningún parámetro.

Ejemplo

class Date(object):
    
    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

date2 = Date.from_string('11-09-2012')
is_date = Date.is_date_valid('11-09-2012')

Explicación

Supongamos un ejemplo de una clase que trata con información de fecha (este será nuestro modelo estándar):

class Date(object):
    
    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

Obviamente, esta clase podría usarse para almacenar información sobre ciertas fechas (sin información de zona horaria; supongamos que todas las fechas se presentan en UTC).

Aquí tenemos __init__, un inicializador típico de instancias de clases de Python, que recibe argumentos como un método de instancia típico, y tiene el primer argumento no opcional ( self) que contiene una referencia a una instancia recién creada.

Método de clase

Tenemos algunas tareas que se pueden realizar muy bien usando classmethods.

Supongamos que queremos crear muchas Dateinstancias de clase que tengan información de fecha proveniente de una fuente externa codificada como una cadena con formato 'dd-mm-aaaa'. Supongamos que tenemos que hacer esto en diferentes lugares del código fuente de nuestro proyecto.

Entonces lo que debemos hacer aquí es:

  1. Analiza una cadena para recibir día, mes y año como tres variables enteras o una tupla de 3 elementos que consta de esa variable.
  2. Cree una instancia Datepasando esos valores a la llamada de inicialización.

Esto se verá así:

day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)

Para este propósito, C++ puede implementar dicha característica con sobrecarga, pero Python carece de esta sobrecarga. En su lugar, podemos usar classmethod. Creemos otro constructor .

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

date2 = Date.from_string('11-09-2012')

Miremos más detenidamente la implementación anterior y revisemos qué ventajas tenemos aquí:

  1. Hemos implementado el análisis de cadenas de fechas en un solo lugar y ahora es reutilizable.
  2. La encapsulación funciona bien aquí (si cree que podría implementar el análisis de cadenas como una función única en otro lugar, esta solución se adapta mucho mejor al paradigma de programación orientada a objetos).
  3. clses la clase misma , no una instancia de la clase. Es genial porque si heredamos nuestra clase, todos los hijos también la Datehabrán definido.from_string

método estático

Qué pasa staticmethod? Es bastante similar, classmethodpero no requiere ningún parámetro obligatorio (como lo hace un método de clase o un método de instancia).

Veamos el siguiente caso de uso.

Tenemos una cadena de fecha que queremos validar de alguna manera. Esta tarea también está vinculada lógicamente a la Dateclase que hemos usado hasta ahora, pero no requiere instanciarla.

Aquí es donde staticmethodpuede resultar útil. Veamos el siguiente fragmento de código:

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

# usage:
is_date = Date.is_date_valid('11-09-2012')

Entonces, como podemos ver por el uso de staticmethod, no tenemos ningún acceso a lo que es la clase; es básicamente solo una función, llamada sintácticamente como un método, pero sin acceso al objeto y sus componentes internos (campos y otros). métodos), que classmethodsí tiene.

Rostyslav Dzinko avatar Aug 29 '2012 14:08 Rostyslav Dzinko

La respuesta de Rostyslav Dzinko es muy apropiada. Pensé que podría resaltar otra razón por la que deberías elegir @classmethodcuando @staticmethodestás creando un constructor adicional.

En el ejemplo , Rostyslav usó @classmethod from_stringcomo Fábrica para crear Dateobjetos a partir de parámetros que de otro modo serían inaceptables. Se puede hacer lo mismo @staticmethodcomo se muestra en el siguiente código:

class Date:
  def __init__(self, month, day, year):
    self.month = month
    self.day   = day
    self.year  = year


  def display(self):
    return "{0}-{1}-{2}".format(self.month, self.day, self.year)


  @staticmethod
  def millenium(month, day):
    return Date(month, day, 2000)

new_year = Date(1, 1, 2013)               # Creates a new Date object
millenium_new_year = Date.millenium(1, 1) # also creates a Date object. 

# Proof:
new_year.display()           # "1-1-2013"
millenium_new_year.display() # "1-1-2000"

isinstance(new_year, Date) # True
isinstance(millenium_new_year, Date) # True

Por tanto, ambos new_yeary millenium_new_yearson instancias de la Dateclase.

Pero, si observa de cerca, el proceso Factory está codificado para crear Dateobjetos sin importar nada. Lo que esto significa es que incluso si la Dateclase tiene subclases, las subclases seguirán creando Dateobjetos simples (sin ninguna propiedad de la subclase). Vea eso en el siguiente ejemplo:

class DateTime(Date):
  def display(self):
      return "{0}-{1}-{2} - 00:00:00PM".format(self.month, self.day, self.year)


datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # False

datetime1.display() # returns "10-10-1990 - 00:00:00PM"
datetime2.display() # returns "10-10-2000" because it's not a DateTime object but a Date object. Check the implementation of the millenium method on the Date class for more details.

datetime2¿ No es una instancia de DateTime? ¿Qué carajo? Bueno, eso se debe al @staticmethoddecorador utilizado.

En la mayoría de los casos, esto no es deseado. Si lo que desea es un método Factory que reconozca la clase que lo llamó, entonces @classmethodes lo que necesita.

Reescribiendo Date.milleniumcomo (esa es la única parte del código anterior que cambia):

@classmethod
def millenium(cls, month, day):
    return cls(month, day, 2000)

garantiza que classno esté codificado sino aprendido. clspuede ser cualquier subclase. El resultado objectserá, con razón, una instancia de cls.
Probemos eso:

datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # True


datetime1.display() # "10-10-1990 - 00:00:00PM"
datetime2.display() # "10-10-2000 - 00:00:00PM"

La razón es, como ya sabes, que @classmethodse usó en lugar de@staticmethod

Yaw Boakye avatar Jan 30 '2013 13:01 Yaw Boakye