Al trabajar con clases en Python, te encontrarás con el decorador @classmethod. Este tiene un propósito único que se distingue claramente de @staticmethod y los métodos de instancia (Instance Method).

En este artículo, clarificaremos qué es @classmethod, y en qué situaciones y cómo debe ser utilizado en comparación con @staticmethod y los métodos de instancia.


1. Tres tipos de métodos: Instance vs. Class vs. Static



Las clases en Python tienen, por defecto, tres tipos de métodos. La mayor diferencia radica en qué recibe como primer argumento el método.

class MyClass:
    # 1. Método de instancia (Instance Method)
    def instance_method(self, arg1):
        # 'self' recibe la instancia de la clase.
        # Puede acceder y modificar atributos de instancia (self.x).
        print(f"Método de instancia llamado con {self} y {arg1}")

    # 2. Método de clase (Class Method)
    @classmethod
    def class_method(cls, arg1):
        # 'cls' recibe la clase misma (MyClass).
        # Se utiliza principalmente para acceder a atributos de clase,
        # o actuar como un 'fábrica' que crea instancias.
        print(f"Método de clase llamado con {cls} y {arg1}")

    # 3. Método estático (Static Method)
    @staticmethod
    def static_method(arg1):
        # No recibe 'self' ni 'cls'.
        # Es una función utilitaria que no tiene relación con el estado de la clase o instancia.
        # Esta función solo pertenece a la clase.
        print(f"Método estático llamado con {arg1}")

# Ejemplo de llamada de método
obj = MyClass()

# Método de instancia (llamado a través de la instancia)
obj.instance_method("Hola") 

# Método de clase (puede ser llamado a través de la clase o instancia)
MyClass.class_method("Mundo")
obj.class_method("Mundo") # Esto también es posible, pero es más claro llamarlo a través de la clase.

# Método estático (puede ser llamado a través de la clase o instancia)
MyClass.static_method("Python")
obj.static_method("Python")

Comparación a simple vista

Clasificación Primer argumento Acceso Uso principal
Método de instancia self (instancia) Atributos de instancia (self.name) Manipular o acceder al estado del objeto
Método de clase cls (clase) Atributos de clase (cls.count) Constructor alternativo (fábrica)
Método estático Ninguno Ninguno Función utilitaria lógicamente relacionada con la clase

2. Uso clave de @classmethod: Método de fábrica

El caso de uso más importante y común de @classmethod es definir un "Constructor alternativo" o "Método de fábrica".

El constructor por defecto, __init__, generalmente recibe argumentos claros para inicializar un objeto. Sin embargo, a veces es necesario crear un objeto de otra manera (por ejemplo, analizando cadenas, diccionarios, valores calculados específicos).

Usando @classmethod, puedes crear otro 'punto de entrada' que devuelva una instancia de la clase además de __init__.

 диаграмма класса

Ejemplo: Creando un objeto Person a partir de una cadena de fecha

Supongamos que la clase Person recibe name y age. ¿Cómo crear un objeto a partir de una cadena en formato 'nombre-año de nacimiento'?

import datetime

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        print(f"Hola, soy {self.name} y tengo {self.age} años.")

    @classmethod
    def from_birth_year(cls, name, birth_year):
        """
        Recibe el año de nacimiento y calcula la edad para crear una instancia de Person.
        """
        current_year = datetime.datetime.now().year
        age = current_year - birth_year

        # 'cls' se refiere a la clase Person.
        # Es equivalente a return Person(name, age), pero,
        # al usar 'cls', se beneficia en caso de herencia. (se explicará a continuación)
        return cls(name, age)

# Usando el constructor por defecto
person1 = Person("Alice", 30)
person1.greet()

# Usando el constructor alternativo creado con @classmethod
person2 = Person.from_birth_year("MadHatter", 1995)
person2.greet()

Resultado:

Hola, soy Alice y tengo 30 años.
Hola, soy MadHatter y tengo 30 años. (basado en el año actual 2025)

En el ejemplo anterior, el método from_birth_year accede a la propia clase Person a través del argumento cls. Luego usa la age calculada para llamar a cls(name, age), lo que es equivalente a llamar a Person(name, age) y devuelve una nueva instancia de Person.


3. @classmethod y herencia



"¿Por qué no puedo simplemente llamar a Person(...)? ¿Por qué usar cls(...)?"

El verdadero poder de @classmethod se revela en la herencia. cls apunta a _la clase exacta_ en la que se llamó al método.

Supongamos que creamos una clase Student que hereda de Person.

class Student(Person):
    def __init__(self, name, age, major):
        super().__init__(name, age) # Llama a __init__ de la clase padre
        self.major = major

    def greet(self):
        # Sobrescritura del método
        print(f"Hola, soy {self.name}, tengo {self.age} años y estudio {self.major}.")

# Llamando al @classmethod del padre desde la clase Student.
student1 = Student.from_birth_year("Jhon", 2005, "Ingeniería de Computadores") 
# ¡Ups, ocurre un TypeError!

# Al llamar a Student.from_birth_year, 
# se ejecuta Person.from_birth_year, 
# donde 'cls' es 'Student'.
# Es decir, return cls(name, age) se convierte en return Student(name, age).
# Sin embargo, __init__ de Student requiere el argumento 'major' adicional.

El código anterior falla porque __init__ de Student necesita major. Pero la clave es que cls apunta a Student y no a Person.

Si from_birth_year hubiera sido @staticmethod, o si se hubiera codificado Person(name, age) directamente, entonces al llamar a Student.from_birth_year, se habría devuelto un objeto Person en lugar de uno de Student.

El uso de @classmethod garantiza que, a través de cls, se crea correctamente una instancia de la clase hija, incluso si es llamada desde la clase hija. (Cabe aclarar que si la firma de __init__ de la clase hija cambia, from_birth_year también tiene que ser sobreescrito.)


4. Resumen

En resumen, aquí están los puntos clave sobre @classmethod.

  1. @classmethod toma cls (la clase misma) como primer argumento.

  2. Su uso principal es como 'constructor alternativo (método de fábrica)'. Se usa al ofrecer diferentes maneras de crear un objeto además de __init__.

  3. Es poderoso cuando se usa con herencia. Al llamarlo desde una clase hija, cls apunta a la clase hija correcta, creando su instancia.

Recuerda que @staticmethod es para funciones utilitarias que no requieren el estado de la clase o instancia en absoluto, @classmethod es para métodos de fábrica que deben acceder a la propia clase, y los métodos de instancia se utilizan para trabajar con el estado del objeto (self), lo que hará que tu código sea mucho más claro y 'pythónico'.