Pregunta Significado de @classmethod y @staticmethod para principiantes?


¿Podría alguien explicarme el significado de @classmethod y @staticmethod en Python? Necesito saber la diferencia y el significado.

Hasta donde yo entiendo, @classmethod le dice a una clase que es un método que debe heredarse en subclases, o ... algo. Sin embargo, ¿qué sentido tiene eso? ¿Por qué no simplemente definir el método de clase sin agregar @classmethod o @staticmethod o cualquier @ definiciones?

tl; dr:  cuando debería usarlos, por qué debería usarlos, y cómo Debería usarlos?

Estoy bastante avanzado con C ++, por lo que usar conceptos de programación más avanzados no debería ser un problema. Siéntete libre de darme un ejemplo correspondiente de C ++ si es posible.


1251
2017-08-29 13:37


origen


Respuestas:


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 el 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 de la información de la fecha (esto es lo que será nuestro estándar para cocinar):

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 clase Python, que recibe argumentos como un típico instancemethod, teniendo el primer argumento no opcional (self) que contiene referencia a una instancia recién creada.

Método de clase

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

Supongamos que queremos crear una gran cantidad de Date instancias de clase que tienen información de fecha proveniente de fuente externa codificada como una cadena de formato siguiente ('dd-mm-aaaa'). Tenemos que hacer eso en diferentes lugares de nuestro código fuente en el proyecto.

Entonces, lo que debemos hacer aquí es:

  1. Analizar 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. Crear 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 ++ tiene características como la sobrecarga, pero Python carece de esa función, así que aquí es cuando classmethod aplica Vamos a crear 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 las ventajas que 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 crees que puedes implementar el análisis de cadenas como una función única en otro lugar, esta solución se ajusta mucho mejor al paradigma OOP).
  3. cls es un objeto que contiene clase en sí, no es una instancia de la clase. Es genial porque si heredamos nuestro Date clase, todos los niños tendrán from_string definido también

Método estático

Qué pasa staticmethod? Es bastante similar a classmethod pero no toma ningún parámetro obligatorio (como 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á ligada lógicamente a Date clase que hemos utilizado hasta ahora, pero aún no requiere instanciación de la misma.

Aquí es donde staticmethod puede ser útil. Veamos la siguiente pieza 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 elementos internos (campos y otros métodos), mientras que classmethod sí.


2154
2017-08-29 14:03



La respuesta de Rostyslav Dzinko es muy apropiada. Pensé que podría resaltar otra razón por la que debes elegir @classmethod encima @staticmethod cuando estás creando un constructor adicional

En el ejemplo anterior, Rostyslav utilizó el @classmethod  from_string como una fábrica para crear Date objetos de parámetros inaceptables. Lo mismo se puede hacer con @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 lo tanto ambos new_year y millenium_new_year son instancias de Date clase.

Pero, si observa de cerca, el proceso de Fábrica está codificado para crear Date objetos sin importar qué. Lo que esto significa es que incluso si el Date la clase está subclasificada, las subclases seguirán creando Date objeto (sin ninguna propiedad de la subclase). Mira 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

datetime2 no es una instancia de DateTime? WTF? Bueno, eso es por el @staticmethod decorador utilizado.

En la mayoría de los casos, esto no es deseado. Si lo que quieres es un método Factory que conozca la clase que lo llamó, entonces @classmethod es lo que necesitas

Reescribiendo el 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)

asegura que el class no está codificado, sino más bien aprendido. cls puede ser cualquier subclase. La resultante object con razón será una instancia de cls. Vamos a probar 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 que, como saben por ahora, @classmethod fue utilizado en lugar de @staticmethod


712
2018-01-30 13:35



@classmethod significa: cuando se llama a este método, pasamos la clase como primer argumento en lugar de la instancia de esa clase (como normalmente hacemos con los métodos). Esto significa que puede usar la clase y sus propiedades dentro de ese método en lugar de una instancia en particular.

@staticmethod significa: cuando se llama a este método, no le pasamos una instancia de la clase (como normalmente hacemos con los métodos). Esto significa que puede poner una función dentro de una clase pero no puede acceder a la instancia de esa clase (esto es útil cuando su método no usa la instancia).


195
2017-08-29 13:40



@staticmethod la función no es más que una función definida dentro de una clase. Se puede llamar sin instanciar primero la clase. Su definición es inmutable a través de la herencia.

  • Python no tiene que instanciar un método obligado para el objeto
  • Facilita la legibilidad del código: ver @staticmethod, sabemos que el método no depende del estado del objeto en sí;

@classmethod también se puede llamar sin instanciar la clase, pero su definición sigue a la Subclase, no a la clase Parent, a través de la herencia, puede ser anulada por subclase. Eso es porque el primer argumento para @classmethodla función siempre debe ser cls (class).

  • Métodos de fábrica, que se utilizan para crear una instancia para una clase utilizando, por ejemplo, algún tipo de preprocesamiento.
  • Métodos estáticos que invocan métodos estáticos: si divide métodos estáticos en varios métodos estáticos, no debe codificar el nombre de la clase, sino usar métodos de clase

aquí es un buen enlace a este tema.


53
2018-04-14 07:12



Uno usaría @classmethod cuando él / ella querría cambiar el comportamiento del método en función de qué subclase llama al método. Recuerde que tenemos una referencia a la clase de llamada en un método de clase.

Al usar estática, querría que el comportamiento permanezca sin cambios en las subclases

Ejemplo:

class Hero:

  @staticmethod
  def say_hello():
     print("Helllo...")

  @classmethod
  def say_class_hello(cls):
     if(cls.__name__=="HeroSon"):
        print("Hi Kido")
     elif(cls.__name__=="HeroDaughter"):
        print("Hi Princess")

class HeroSon(Hero):
  def say_son_hello(self):
     print("test  hello")



class HeroDaughter(Hero):
  def say_daughter_hello(self):
     print("test  hello daughter")


testson = HeroSon()

testson.say_class_hello() #Output: "Hi Kido"

testson.say_hello() #Outputs: "Helllo..."

testdaughter = HeroDaughter()

testdaughter.say_class_hello() #Outputs: "Hi Princess"

testdaughter.say_hello() #Outputs: "Helllo..."

27
2017-08-21 07:18



Una pequeña compilación

@staticmethod Una forma de escribir un método dentro de una clase sin referencia al objeto al que se está llamando. Así que no es necesario pasar un argumento implícito como self o cls. Está escrito exactamente igual que escrito fuera de la clase, pero no sirve para Python, porque si necesitas encapsular un método dentro de una clase, ya que este método debe ser parte de esa clase, @staticmethod es útil en ese aspecto. caso.

@classmethod Es importante cuando desea escribir un método de fábrica y por este atributo (s) personalizado (s) se pueden adjuntar en una clase. Este atributo (s) puede ser anulado en la clase heredada.

Una comparación entre estos dos métodos puede ser la siguiente

Table


26
2017-07-19 16:43



El significado de @classmethod y @staticmethod?

  • Un método es una función en el espacio de nombres de un objeto, accesible como un atributo.
  • Un método regular (es decir, instancia) obtiene la instancia (generalmente lo llamamos self) como el primer argumento implícito.
  • UN clase método obtiene la clase (generalmente lo llamamos cls) como el primer argumento implícito.
  • UN estático método no obtiene primer argumento implícito (como una función regular).

¿Cuándo debería usarlos, por qué debería usarlos y cómo debería usarlos?

Tu no necesitar cualquiera decorador. Pero según el principio de que debe minimizar el número de argumentos para las funciones (ver Clean Coder), son útiles para hacer justamente eso.

class Example(object):

    def regular_instance_method(self):
        """A function of an instance has access to every attribute of that 
        instance, including its class (and its attributes.)
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_f(self)

    @classmethod
    def a_class_method(cls):
        """A function of a class has access to every attribute of the class.
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_g(cls)

    @staticmethod
    def a_static_method():
        """A static method has no information about instances or classes
        unless explicitly given. It just lives in the class (and thus its 
        instances') namespace.
        """
        return some_function_h()

Para ambos métodos de instancia y métodos de clase, no aceptar al menos un argumento es un TypeError, pero no entender la semántica de ese argumento es un error del usuario.

(Definir some_function, por ejemplo,

some_function_h = some_function_g = some_function_f = lambda x=None: x

y esto funcionará.)

búsquedas punteadas en instancias y clases:

Una búsqueda punteada en una instancia se realiza en este orden: buscamos:

  1. un descriptor de datos en el espacio de nombres de la clase (como una propiedad)
  2. datos en la instancia __dict__
  3. un descriptor que no sea de datos en el espacio de nombres de la clase (métodos).

Tenga en cuenta que una búsqueda con puntos en una instancia se invoca así:

instance = Example()
instance.regular_instance_method 

y los métodos son atributos que se pueden llamar:

instance.regular_instance_method()

métodos de instancia

El argumento, self, se da implícitamente a través de la búsqueda punteada.

Debe acceder a los métodos de instancia desde las instancias de la clase.

>>> instance = Example()
>>> instance.regular_instance_method()
<__main__.Example object at 0x00000000399524E0>

métodos de clase

El argumento, cls, se da implícitamente a través de búsqueda punteada.

Puede acceder a este método a través de una instancia o clase (o subclases).

>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>

métodos estáticos

No hay argumentos dados implícitamente. Este método funciona como cualquier función definida (por ejemplo) en el espacio de nombres de un módulo, excepto que puede buscarse

>>> print(instance.a_static_method())
None

Una vez más, ¿cuándo debería usarlos? ¿Por qué debería usarlos?

Cada uno de estos es progresivamente más restrictivo en la información que pasan el método en comparación con los métodos de instancia.

Úselos cuando no necesite la información.

Esto hace que sus funciones y métodos sean más fáciles de razonar y de evaluar la unidad.

¿Cuál es más fácil de razonar?

def function(x, y, z): ...

o

def function(y, z): ...

o

def function(z): ...

Las funciones con menos argumentos son más fáciles de razonar. También son más fáciles de probar.

Estos son similares a la instancia, clase y métodos estáticos. Teniendo en cuenta que cuando tenemos una instancia, también tenemos su clase, de nuevo, pregúntate a ti mismo, ¿cuál es más fácil de razonar ?:

def an_instance_method(self, arg, kwarg=None):
    cls = type(self)             # Also has the class of instance!
    ...

@classmethod
def a_class_method(cls, arg, kwarg=None):
    ...

@staticmethod
def a_static_method(arg, kwarg=None):
    ...

Ejemplos integrados

Aquí hay algunos de mis ejemplos integrados favoritos:

los str.maketrans método estático era una función en el string módulo, pero es mucho más conveniente que sea accesible desde str espacio de nombres

>>> 'abc'.translate(str.maketrans({'a': 'b'}))
'bbc'

los dict.fromkeys El método de clase devuelve un nuevo diccionario instanciado a partir de una serie de claves:

>>> dict.fromkeys('abc')
{'a': None, 'c': None, 'b': None}

Cuando se subclasifica, vemos que obtiene la información de la clase como un método de clase, que es muy útil:

>>> class MyDict(dict): pass
>>> type(MyDict.fromkeys('abc'))
<class '__main__.MyDict'> 

Mi consejo - Conclusión

Utilice métodos estáticos cuando no necesite los argumentos de clase o instancia, pero la función está relacionada con el uso del objeto, y es conveniente que la función esté en el espacio de nombres del objeto.

Use métodos de clase cuando no necesite información de instancia, pero necesite la información de clase, tal vez para su otra clase o métodos estáticos, o tal vez como constructor. (No codificaría la clase para que las subclases se puedan usar aquí).


18
2017-10-24 16:09



Soy un principiante en este sitio, he leído todas las respuestas anteriores y obtuve la información que deseo. Sin embargo, no tengo el derecho de votar. Así que quiero comenzar en StackOverflow con la respuesta tal como lo entiendo.

  • @staticmethod no necesita self o cls como el primer parámetro del método
  • @staticmethod y @classmethod función envuelta podría llamarse por instancia o variable de clase
  • @staticmethod función decorada impacto algún tipo 'propiedad inmutable' que la herencia de subclase no puede sobrescribir su función de clase base que está envuelto por un @staticmethod decorador.
  • @classmethod need cls (nombre de clase, puede cambiar el nombre de la variable si lo desea, pero no se recomienda) como primer parámetro de función
  • @classmethod siempre utilizado por subclase, la herencia de subclase puede cambiar el efecto de la función de clase base, es decir @classmethod la función de clase base envolvente podría sobrescribirse por diferentes subclases.

1
2017-07-23 11:38



Una forma ligeramente diferente de pensar sobre esto que podría ser útil para alguien ... Un método de clase se usa en una superclase para definir cómo se debe comportar ese método cuando es invocado por diferentes clases secundarias. Se utiliza un método estático cuando queremos devolver lo mismo independientemente de la clase hija a la que llamamos.


0
2018-01-24 22:59