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:
- un descriptor de datos en el espacio de nombres de clase (como una propiedad)
- datos en la instancia
__dict__
- 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:
- 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.
- 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í:
- Hemos implementado el análisis de cadenas de fecha en un solo lugar y ahora es reutilizable.
- 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).
-
cls
es un objeto que contiene la clase en sí , no una instancia de la clase. Es muy bueno porque si heredamos nuestra claseDate
, todos los hijos también tendránfrom_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