@abstractmethod-1

 

@classmethod en Python

Un classmethod es un método que pertenece a la clase, no a las instancias. Recibe la clase como primer parámetro (por convención llamado cls) en lugar de la instancia (self).

Diferencias clave:

  • Método normal: Recibe self (la instancia)

  • Classmethod: Recibe cls (la clase)

  • Puede ser llamado desde la clase sin crear una instancia

Ejemplo muy sencillo:

python
class Persona:
    especie = "Humano"  # Atributo de clase
    
    def __init__(self, nombre, edad):
        self.nombre = nombre  # Atributo de instancia
        self.edad = edad
    
    # Método normal - necesita una instancia
    def presentarse(self):
        return f"Hola, soy {self.nombre} y tengo {self.edad} años"
    
    # Classmethod - puede usarse sin instancia
    @classmethod
    def que_especie(cls):
        return f"Somos de la especie: {cls.especie}"
    
    # Classmethod para crear objetos de forma alternativa
    @classmethod
    def desde_nacimiento(cls, nombre, año_nacimiento):
        edad = 2024 - año_nacimiento
        return cls(nombre, edad)  # Crea una nueva instancia

# Uso
print(Persona.que_especie())  # ¡Sin crear objeto!
# Output: Somos de la especie: Humano

# Método normal necesita instancia
persona1 = Persona("Ana", 25)
print(persona1.presentarse())  # Hola, soy Ana y tengo 25 años

# Usando classmethod como constructor alternativo
persona2 = Persona.desde_nacimiento("Carlos", 1995)
print(persona2.presentarse())  # Hola, soy Carlos y tengo 29 años

# También se puede llamar desde una instancia
print(persona1.que_especie())  # Somos de la especie: Humano

Casos de uso comunes:

1. Constructores alternativos

python
class Fecha:
    def __init__(self, dia, mes, año):
        self.dia = dia
        self.mes = mes
        self.año = año
    
    @classmethod
    def desde_string(cls, fecha_str):
        """Crea Fecha desde string 'dd-mm-aaaa'"""
        dia, mes, año = map(int, fecha_str.split('-'))
        return cls(dia, mes, año)
    
    @classmethod
    def hoy(cls):
        """Crea Fecha con la fecha de hoy"""
        from datetime import date
        hoy = date.today()
        return cls(hoy.day, hoy.month, hoy.year)

# Uso
fecha1 = Fecha(15, 3, 2024)  # Constructor normal
fecha2 = Fecha.desde_string("20-05-2024")  # Constructor alternativo
fecha3 = Fecha.hoy()  # Otro constructor alternativo

2. Modificar atributos de clase

python
class Contador:
    contador_global = 0
    
    def __init__(self):
        self.id = Contador.contador_global
        Contador.incrementar_contador()
    
    @classmethod
    def incrementar_contador(cls):
        cls.contador_global += 1
    
    @classmethod
    def reiniciar_contador(cls):
        cls.contador_global = 0

# Uso
obj1 = Contador()
obj2 = Contador()
print(Contador.contador_global)  # 2

Contador.reiniciar_contador()
obj3 = Contador()
print(Contador.contador_global)  # 1

En resumen:

  • @classmethod trabaja con la clase (no con instancias)

  • Recibe cls como primer parámetro

  • No necesita crear un objeto para usarlo

  • Útil para: constructores alternativos, manipular atributos de clase, métodos que no dependen de instancias específicas

¿Te queda claro? ¡Es mucho más simple de lo que parece!


@classmethod en Clase Suma: El Contador Global 🧮

Excelente pregunta! Vamos con tu ejemplo de la clase Suma para entenderlo claramente.

Primero, entendamos el problema

python
class Suma:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
    def calcular(self):
        return self.a + self.b

# Diferentes instancias, diferentes resultados
suma1 = Suma(2, 3)
suma2 = Suma(5, 7)
suma3 = Suma(10, 15)

print(suma1.calcular())  # 5
print(suma2.calcular())  # 12  
print(suma3.calcular())  # 25

Hasta aquí: Cada instancia es independiente.

Ahora agreguemos @classmethod

python
class Suma:
    # Atributo de CLASE (compartido por todos)
    total_sumas = 0
    historial = []
    
    def __init__(self, a, b):
        self.a = a
        self.b = b
        # Contamos CADA nueva suma creada
        Suma.total_sumas += 1
    
    def calcular(self):
        resultado = self.a + self.b
        # Guardamos en el historial
        Suma.agregar_al_historial(f"{self.a} + {self.b} = {resultado}")
        return resultado
    
    # @classmethod para trabajar con datos de TODAS las sumas
    @classmethod
    def obtener_estadisticas(cls):
        return f"Total de sumas realizadas: {cls.total_sumas}"
    
    @classmethod
    def obtener_historial(cls):
        return cls.historial
    
    @classmethod
    def agregar_al_historial(cls, operacion):
        cls.historial.append(operacion)
    
    @classmethod
    def reiniciar_contador(cls):
        cls.total_sumas = 0
        cls.historial = []
    
    # @classmethod como método de fábrica
    @classmethod
    def desde_lista(cls, numeros):
        if len(numeros) >= 2:
            return cls(numeros[0], numeros[1])
        else:
            return cls(0, 0)
    
    @classmethod 
    def desde_cadena(cls, cadena):
        # Convierte "5+3" en Suma(5, 3)
        try:
            a, b = map(int, cadena.split('+'))
            return cls(a, b)
        except:
            return cls(0, 0)

¿Cómo funciona esto en la práctica?

python
# Creamos sumas de diferentes maneras
suma1 = Suma(2, 3)
suma2 = Suma(5, 7)
suma3 = Suma.desde_lista([10, 15])  # Usando classmethod
suma4 = Suma.desde_cadena("8+4")    # Usando classmethod

# Cada instancia calcula su propia suma
print(suma1.calcular())  # 5
print(suma2.calcular())  # 12
print(suma3.calcular())  # 25  
print(suma4.calcular())  # 12

# Pero los @classmethod ven el panorama completo
print(Suma.obtener_estadisticas())  # Total de sumas realizadas: 4
print(Suma.obtener_historial())
# ['2 + 3 = 5', '5 + 7 = 12', '10 + 15 = 25', '8 + 4 = 12']

Analogía: La Oficina de Contabilidad 📊

text
OFICINA CONTABLE (Clase Suma)

TRABAJADORES INDIVIDUALES (instancias)
├── suma1: 2 + 3 = 5
├── suma2: 5 + 7 = 12  
└── suma3: 10 + 15 = 25

GERENTE (@classmethod) - Ve el panorama completo
├── total_sumas = 3
├── historial = ["2+3=5", "5+7=12", "10+15=25"]
└── Puede reiniciar todo o crear nuevos trabajadores

Puntos clave entendidos:

✅ SÍ puedes afectar a todas las instancias:

python
# Esto afecta a TODAS las sumas existentes y futuras
Suma.reiniciar_contador()

# Ahora todas las instancias verán los cambios
print(Suma.total_sumas)  # 0
print(suma1.total_sumas) # 0 (¡sí, desde la instancia también!)

✅ SÍ puedes crear métodos que no necesitan instancias:

python
# Sin crear ninguna suma, puedo usar classmethods
print(Suma.obtener_estadisticas())  # Funciona sin instancias!

# Puedo crear instancias de formas alternativas
suma_nueva = Suma.desde_cadena("20+30")  # Sin usar __init__ directamente

❌ NO puedes acceder a datos específicos de instancias:

python
class Suma:
    @classmethod
    def metodo_mal_uso(cls):
        # ESTO NO FUNCIONA - classmethod no sabe sobre instancias específicas
        # print(self.a)  # ❌ Error - no hay self!
        # print(cls.a)   # ❌ Error - cls es la clase, no una instancia
        pass

Resumen final:

@classmethod te permite:

  1. Trabajar con datos GLOBALES de la clase (compartidos por todos)

  2. Crear "métodos de fábrica" para instanciar de formas alternativas

  3. Acceder a funcionalidad sin necesidad de crear instancias

  4. Modificar comportamiento que afecta a TODAS las instancias

Pero NO puede:

  • Acceder a datos específicos de una instancia individual

  • Reemplazar métodos que necesitan trabajar con datos de instancia

¡Es como tener un gerente que ve todas las operaciones pero no hace las sumas individuales!


Comentarios

Entradas populares de este blog

¿Qué es un Closure?

4 tipos de colecciones de datos más

Funciones en Python: con y sin paréntesis