Significado de @classmethod y @staticmethod para principiantes [duplicado]
¿Qué significan @classmethod
y @staticmethod
significan en Python y en qué se diferencian? ¿Cuándo debo usarlos, por qué debo usarlos y cómo debo usarlos?
Hasta donde tengo entendido, @classmethod
le 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 @classmethod
ninguna @staticmethod
definición @
?
Aunque classmethod
y staticmethod
son bastante similares, hay una ligera diferencia en el uso de ambas entidades: classmethod
debe tener una referencia a un objeto de clase como primer parámetro, mientras que staticmethod
no 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 classmethod
s.
Supongamos que queremos crear muchas Date
instancias 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:
- 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.
- Cree una instancia
Date
pasando 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í:
- Hemos implementado el análisis de cadenas de fechas en un solo lugar y ahora es reutilizable.
- 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).
cls
es la clase misma , no una instancia de la clase. Es genial porque si heredamos nuestra clase, todos los hijos también laDate
habrán definido.from_string
método estático
Qué pasa staticmethod
? Es bastante similar, classmethod
pero 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 Date
clase que hemos usado hasta ahora, pero no requiere instanciarla.
Aquí es donde staticmethod
puede 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 classmethod
sí tiene.
La respuesta de Rostyslav Dzinko es muy apropiada. Pensé que podría resaltar otra razón por la que deberías elegir @classmethod
cuando @staticmethod
estás creando un constructor adicional.
En el ejemplo , Rostyslav usó @classmethod
from_string
como Fábrica para crear Date
objetos a partir de parámetros que de otro modo serían inaceptables. Se puede hacer lo mismo @staticmethod
como 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_year
y millenium_new_year
son instancias de la Date
clase.
Pero, si observa de cerca, el proceso Factory está codificado para crear Date
objetos sin importar nada. Lo que esto significa es que incluso si la Date
clase tiene subclases, las subclases seguirán creando Date
objetos 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 @staticmethod
decorador 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 @classmethod
es lo que necesita.
Reescribiendo Date.millenium
como (esa es la única parte del código anterior que cambia):
@classmethod
def millenium(cls, month, day):
return cls(month, day, 2000)
garantiza que class
no esté codificado sino aprendido. cls
puede ser cualquier subclase. El resultado object
será, 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 @classmethod
se usó en lugar de@staticmethod