staticmethod que classmethod python oop static-methods class-method

python - que - ¿Significado de @classmethod y @staticmethod para principiante?



python 3 classmethod (11)

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

Según tengo entendido, @classmethod le dice a una clase que es un método que debe heredarse en subclases, o ... algo. Sin embargo, ¿cuál es el punto de eso? ¿Por qué no definir el método de la clase sin agregar @classmethod o @staticmethod o ninguna de las definiciones @ ?

tl; dr: ¿ cuándo debo usarlos, por qué debo usarlos y cómo debo usarlos?

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


¿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 (por lo general, lo llamamos uno self ) como el primer argumento implícito.
  • Un método de clase obtiene la clase (generalmente lo llamamos cls ) como el primer argumento implícito.
  • Un método estático no obtiene ningún primer argumento implícito (como una función regular).

¿Cuándo debo usarlos, por qué debo usarlos y cómo debo usarlos?

No necesitas ninguno de los dos decoradores. Pero en el principio de que debe minimizar el número de argumentos de las funciones (consulte Clean Coder), son útiles para hacer precisamente 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()

Tanto para los métodos de instancia como para los métodos de clase, no aceptar al menos un argumento es un error de tipo, 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 de puntos en instancias y clases:

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

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

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

instance = Example() instance.regular_instance_method

Y los métodos son atributos exigibles:

instance.regular_instance_method()

métodos de instancia

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

Debe acceder a los métodos de instancia de 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 la búsqueda por puntos.

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

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

métodos estáticos

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

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

Una vez más, ¿cuándo debo usarlos, por qué debo usarlos?

Cada uno de estos son progresivamente más restrictivos en la información que pasan el método versus los métodos de instancia.

Úsalos cuando no necesites la información.

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

¿Sobre 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, la clase y los métodos estáticos. Teniendo en cuenta que cuando tenemos una instancia, también tenemos su clase, una vez más, pregúntese, ¿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 incorporados

Aquí hay un par de mis ejemplos incorporados favoritos:

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

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

El método de clase dict.fromkeys devuelve un nuevo diccionario creado a partir de una iterable de claves:

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

Cuando se hace una subclase, vemos que obtiene la información de clase como un método de clase, lo cual es muy útil:

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

Mi consejo - Conclusión

Use 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 un constructor. (No codificarías la clase para que las subclases pudieran usarse aquí).


Cuándo usar cada

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

  • Python no tiene que crear una instancia de un método enlazado para el objeto.
  • Facilita la legibilidad del código: al ver el método @estático , sabemos que el método no depende del estado del objeto en sí;

@classmethod función @classmethod también se puede @classmethod sin crear una instancia de la clase, pero su definición sigue a Subclase, no a la Clase principal, por herencia, se puede anular por subclase. Esto se debe a que el primer argumento para la función @classmethod 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 llaman a métodos estáticos : si divide los métodos estáticos en varios métodos estáticos, no debe codificar el nombre de la clase, sino usar los métodos de clase.

here hay un buen enlace a este tema.


Aunque el classmethod y el staticmethod son bastante similares, hay una ligera diferencia en el uso de ambas entidades: el classmethod debe tener una referencia a un objeto de clase como primer parámetro, mientras que el staticmethod 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 la información de fecha (este será nuestro modelo):

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

Esta clase obviamente 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 las instancias de la clase Python, que recibe argumentos como un __init__ típico de instancemethod , con 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 hacer bien usando classmethod s.

Supongamos que queremos crear muchas instancias de la clase Date con información de fecha que proviene de una fuente externa codificada como una cadena con formato ''dd-mm-aaaa''. Supongamos que tenemos que hacer esto en diferentes lugares en el código fuente de nuestro proyecto.

Entonces, lo que debemos hacer aquí es:

  1. Analice una cadena para recibir el día, el mes y el año como tres variables enteras o una tupla de 3 elementos que consta de esa variable.
  2. Instalar la Date pasando esos valores a la llamada de inicialización.

Esto se verá como:

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

Para este propósito, C ++ puede implementar esta característica con sobrecarga, pero Python carece de esta sobrecarga. En su lugar, podemos utilizar el classmethod . 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'')

Veamos más detenidamente la implementación anterior y revisemos las ventajas que tenemos aquí:

  1. Hemos implementado el análisis de cadenas de fecha en un solo lugar y ahora es reutilizable.
  2. La encapsulación funciona bien aquí (si piensa que podría 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 la clase en sí , no una instancia de la clase. Es muy bueno porque si heredamos nuestra clase Date , todos los hijos también tendrán from_string definido.

Método estático

¿Qué pasa con el staticmethod ? Es bastante similar al classmethod de classmethod pero no toma ningún parámetro obligatorio (como lo hace un método de clase o 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 clase Date que hemos usado hasta ahora, pero no requiere una instanciación de la misma.

Aquí es donde el staticmethod puede ser ú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 del staticmethod , no tenemos ningún acceso a lo que es la clase --- es básicamente una función, llamada sintácticamente como un método, pero sin acceso al objeto y sus aspectos internos (campos y campos). otros métodos), mientras que classmethod lo hace.


El método de clase puede modificar el estado de la clase, se vincula a la clase y contiene cls como parámetro.

El método estático no puede modificar el estado de la clase, está vinculado a la clase y no conoce la clase o la instancia

class empDetails: def __init__(self,name,sal): self.name=name self.sal=sal @classmethod def increment(cls,name,none): return cls(''yarramsetti'',6000 + 500) @staticmethod def salChecking(sal): return sal > 6000 emp1=empDetails(''durga prasad'',6000) emp2=empDetails.increment(''yarramsetti'',100) # output is ''durga prasad'' print emp1.name # output put is 6000 print emp1.sal # output is 6500,because it change the sal variable print emp2.sal # output is ''yarramsetti'' it change the state of name variable print emp2.name # output is True, because ,it change the state of sal variable print empDetails.salChecking(6500)


En resumen, @classmehtod convierte un método normal a un método de fábrica.

Explorémoslo con un ejemplo:

class PythonBook: def __init__(self, name, author): self.name = name self.author = author def __repr__(self): return f''Book: {self.name}, Author: {self.author}''

Sin un método de clase @, deberías trabajar para crear instancias una por una y se ejecutarán con un scart.

book1 = PythonBook(''Learning Python'', ''Mark Lutz'') In [20]: book1 Out[20]: Book: Learning Python, Author: Mark Lutz book2 = PythonBook(''Python Think'', ''Allen B Dowey'') In [22]: book2 Out[22]: Book: Python Think, Author: Allen B Dowey

Como por ejemplo con @classmethod

class PythonBook: def __init__(self, name, author): self.name = name self.author = author def __repr__(self): return f''Book: {self.name}, Author: {self.author}'' @classmethod def book1(cls): return cls(''Learning Python'', ''Mark Lutz'') @classmethod def book2(cls): return cls(''Python Think'', ''Allen B Dowey'')

Pruébalo:

In [31]: PythonBook.book1() Out[31]: Book: Learning Python, Author: Mark Lutz In [32]: PythonBook.book2() Out[32]: Book: Python Think, Author: Allen B Dowey

¿Ver? Las instancias se crean correctamente dentro de una definición de clase y se recopilan juntas.

En conclusión, @classmethod decorator convierte un método convencional a un método de fábrica. El uso de classmethods hace posible agregar tantos constructores alternativos como sea necesario.


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

En el ejemplo anterior, Rostyslav usó @classmethod from_string como Factory para crear objetos de Date partir de parámetros de otro modo 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, tanto new_year como millenium_new_year son instancias de la clase Date .

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

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

En la mayoría de los casos, esto no es deseado. Si lo que quiere es un método de fábrica que sea consciente de la clase que lo llamó, entonces @classmethod es lo que necesita.

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 la class no esté codificada, sino que más bien aprendida. cls puede ser cualquier subclase. El object resultante será con razón 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 ya saben, se usó @staticmethod lugar de @staticmethod


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

  • @staticmethod no necesita self o cls como primer parámetro del método
  • @staticmethod funciones @staticmethod y @classmethod se pueden llamar por instancia o variable de clase
  • @staticmethod función decorada de @staticmethod afecta a una especie de ''propiedad inmutable'' que la herencia de subclase no puede sobrescribir en su función de clase base que está envuelta por un decorador de @staticmethod .
  • @classmethod need cls (Nombre de la clase, puede cambiar el nombre de la variable si lo desea, pero no se recomienda) como el primer parámetro de la función
  • @classmethod siempre se usa de manera subclase, la herencia de subclase puede cambiar el efecto de la función de clase base, es decir, la función de clase base envuelta @classmethod podría ser sobrescrita por diferentes subclases.

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


Uno usaría @classmethod cuando quisiera cambiar el comportamiento del método en función de la subclase que está llamando al método. recuerda que tenemos una referencia a la clase que llama en un método de clase.

Mientras usa la estática, desearí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..."


@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).


Un poco de 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 hay necesidad de pasar argumentos implícitos como self o cls. Se escribe exactamente de la misma manera que se escribe fuera de la clase, pero no es de ninguna utilidad en Python porque si necesita encapsular un método dentro de una clase, ya que este método debe ser parte de esa clase, @staticmethod es muy útil. caso.

@classmethod Es importante cuando desea escribir un método de fábrica y, por este atributo (s) personalizado (s), se puede 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