Son los métodos más comunes en Python. Reciben automáticamente como primer argumento la instancia misma (self), lo que les permite acceder y modificar tanto atributos de instancia como de clase.
class Calculadora:
factor = 10 # atributo de clase
def __init__(self, base):
self.base = base # atributo de instancia
def multiplicar(self, numero):
resultado = self.base * numero * Calculadora.factor
return resultado
Para invocarlos, es necesario crear una instancia previamente:
calc = Calculadora(5)
print(calc.multiplicar(3)) # 150
print(Calculadora.multiplicar(calc, 3)) # 150 (forma explícita)
Un método estático es una función definida dentro del cuerpo de una clase, pero que no recibe self ni cls automáticamente. Se comporta como una función ordinaria que simplemente reside en el espacio de nombres de la clase. No puede acceder a atributos de instancia ni de clase de forma directa.
Se puede definir mediante la función incorporada staticmethod() o, preferentemente, con el decorador @staticmethod.
class Utilidad:
descripcion = "Clase de utilidad"
def __init__(self):
self.nombre = "Instancia de utilidad"
@staticmethod
def saludar(mensaje):
return f"Mensaje: {mensaje}"
El método puede llamarse tanto desde la clase como desde una instanica, sin diferencia alguna:
print(Utilidad.saludar("Hola")) # Mensaje: Hola
u = Utilidad()
print(u.saludar("Hola desde instancia")) # Mensaje: Hola desde instancia
Como se observa, no existe acceso a descripcion ni a nombre dentro del método estático, pues no dispone de referencias automáticas a la clase ni a la instancia.
Los métodos de clase reciben como primer argumento la clase misma (cls), en lugar de una instancia. Esto les permite acceder y modificar atributos de clase, pero no atributos de instancia. Son útiles cuando se necesita lógica relacionada con la clase en general: contadores de instancias, factories, gestión de estado compartido, entre otros.
class Contador:
total = 0
def __init__(self, etiqueta):
self.etiqueta = etiqueta
Contador.total += 1
@classmethod
def obtener_total(cls):
return f"Instancias creadas: {cls.total}"
Al consultar el método, siempre se opera sobre la clase, sin importar si se invoca desde la instancia:
c1 = Contador("primero")
c2 = Contador("segundo")
print(c1.obtener_total()) # Instancias creadas: 2
print(Contador.obtener_total()) # Instancias creadas: 2
Si se crea una subclase, cls hará referencia a la subclase, lo cual los hace especialmente útiles para patrones como el factory method.
El siguiente ejemplo reúne los tres tipos en una sola clase para apreciar sus diferencias:
class Demo:
valor_clase = 100
def __init__(self, valor_instancia):
self.valor_instancia = valor_instancia
# Método de instancia
def metodo_instancia(self, x):
self.valor_instancia = x
return f"Instancia actualizada a {self.valor_instancia}"
# Método estático
@staticmethod
def metodo_estatico(x):
return f"Operación estática con {x}"
# Método de clase
@classmethod
def metodo_clase(cls, x):
return f"Clase: {cls.__name__}, valor_clase: {cls.valor_clase}, parámetro: {x}"
Formas de invocación:
d = Demo(10)
print(d.metodo_instancia(20)) # Instancia actualizada a 20
print(Demo.metodo_instancia(d, 20)) # Instancia actualizada a 20
print(d.metodo_estatico(5)) # Operación estática con 5
print(Demo.metodo_estatico(5)) # Operación estática con 5
print(d.metodo_clase(7)) # Clase: Demo, valor_clase: 100, parámetro: 7
print(Demo.metodo_clase(7)) # Clase: Demo, valor_clase: 100, parámetro: 7
Un detalle importante: al modificar valor_clase directamente en la clase, el método de clase refeljará el cambio. En cambio, si se asigna un atributo con el mismo nombre en una instancia, cls.valor_clase seguirá leyendo el valor del atributo de clase:
d.valor_clase = 999
print(d.metodo_clase(0)) # valor_clase sigue siendo 100
Demo.valor_clase = 999
print(d.metodo_clase(0)) # ahora valor_clase es 999