W3docs

Python @staticmethod y @classmethod

Aprende cómo funcionan los decoradores @staticmethod y @classmethod de Python, cuándo usar cada uno y cómo escribir métodos de fábrica y funciones auxiliares.

Python asigna a cada método dentro de una clase uno de tres estilos de vinculación: puede estar vinculado a una instancia, a la clase en sí, o a ninguna. Los decoradores @classmethod y @staticmethod controlan estos dos últimos estilos.

Este capítulo cubre:

  • Los tres tipos de métodos y lo que los diferencia
  • @staticmethod — una función normal almacenada dentro de una clase
  • @classmethod — un método que recibe la clase como primer argumento
  • Métodos de fábrica: el uso más común en el mundo real de @classmethod
  • Constructores alternativos y cómo interactúan con la herencia
  • Cuándo elegir @staticmethod vs @classmethod vs una función a nivel de módulo
  • Errores comunes

Antes de leer, asegúrate de estar familiarizado con las clases y objetos de Python y la herencia en Python. Para el acceso a atributos calculados, consulta @property. Para un análisis profundo de cómo funcionan los decoradores en general, consulta Decoradores de Python.

Los Tres Tipos de Métodos

Antes de examinar cada decorador, aquí tienes una comparación lado a lado:

Método de instancia@classmethod@staticmethod
Primer parámetroself (la instancia)cls (la clase)ninguno
¿Recibe la instancia?NoNo
¿Recibe la clase?Vía type(self)Sí (directamente)No
Se llama sobre una instancia
Se llama sobre la claseSí (pero self falta)
Uso típicoOperar sobre datos de instanciaMétodos de fábrica, estado a nivel de claseFunciones de utilidad/auxiliares
class Demo:
    def instance_method(self):
        return f"instance method — self is {self}"

    @classmethod
    def class_method(cls):
        return f"class method — cls is {cls}"

    @staticmethod
    def static_method():
        return "static method — no self, no cls"

d = Demo()
print(d.instance_method())   # instance method — self is <__main__.Demo object at 0x...>
print(d.class_method())      # class method — cls is <class '__main__.Demo'>
print(d.static_method())     # static method — no self, no cls

# All three can also be called directly on the class:
print(Demo.class_method())   # class method — cls is <class '__main__.Demo'>
print(Demo.static_method())  # static method — no self, no cls

@staticmethod

Un método estático es el más simple de los tres. Es simplemente una función normal que reside dentro del espacio de nombres de una clase. Python no pasa self ni cls automáticamente.

class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

    @staticmethod
    def is_even(n):
        return n % 2 == 0

print(MathUtils.add(3, 4))   # 7
print(MathUtils.is_even(10)) # True

Cuándo usar @staticmethod

Usa @staticmethod cuando un auxiliar pertenece lógicamente a una clase — por claridad, agrupación o espacio de nombres — pero no necesita leer ni modificar el estado de la instancia ni el de la clase:

  • Auxiliares de validación llamados antes de construir un objeto.
  • Funciones puras de conversión o cálculo que solo tienen sentido en el contexto de una clase.
  • Funciones de utilidad usadas por varios métodos de la misma clase pero en ningún otro lugar.
class Temperature:
    def __init__(self, celsius):
        if not Temperature._is_valid(celsius):
            raise ValueError(f"Temperature {celsius} °C is below absolute zero")
        self.celsius = celsius

    @staticmethod
    def _is_valid(celsius):
        return celsius >= -273.15

    @staticmethod
    def celsius_to_fahrenheit(celsius):
        return celsius * 9 / 5 + 32

t = Temperature(100)
print(Temperature.celsius_to_fahrenheit(100))  # 212.0
print(Temperature._is_valid(-300))             # False

Observa que _is_valid tiene el prefijo _ para indicar que es interno a la clase. Los llamadores que solo necesitan objetos Temperature nunca lo ven — simplemente obtienen un ValueError si pasan un valor imposible.

@staticmethod vs una función a nivel de módulo

Una función a nivel de módulo y un @staticmethod son casi idénticos en comportamiento. La diferencia es dónde vive la función:

  • Si la función solo es relevante para Temperature (o se llama exclusivamente desde dentro de Temperature), ponla dentro de la clase como @staticmethod.
  • Si es una utilidad general usada en todo tu módulo, ponla a nivel de módulo.

No hay diferencia de rendimiento. Esta es puramente una decisión organizativa.

@classmethod

Un método de clase recibe la clase como primer argumento (llamado cls por convención — pero al igual que self, el nombre es una convención, no una palabra clave). Como tiene una referencia a la clase, puede:

  • Leer o modificar atributos a nivel de clase.
  • Crear y devolver nuevas instancias de la clase (métodos de fábrica).
  • Funcionar correctamente con subclases (fábricas polimórficas).
class Counter:
    _count = 0  # class-level attribute

    def __init__(self):
        Counter._count += 1

    @classmethod
    def get_count(cls):
        return cls._count

    @classmethod
    def reset(cls):
        cls._count = 0

Counter()
Counter()
Counter()
print(Counter.get_count())  # 3
Counter.reset()
print(Counter.get_count())  # 0

Métodos de fábrica — el caso de uso más importante

El uso más común y valioso de @classmethod es como método de fábrica (también llamado constructor alternativo). Un método de fábrica crea instancias a partir de diferentes tipos de entrada sin saturar __init__ con lógica condicional.

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

    def __repr__(self):
        return f"Date({self.year}, {self.month}, {self.day})"

    @classmethod
    def from_string(cls, date_string):
        """Create a Date from an ISO 8601 string, e.g. '2024-03-15'."""
        year, month, day = (int(p) for p in date_string.split("-"))
        return cls(year, month, day)

    @classmethod
    def from_tuple(cls, date_tuple):
        """Create a Date from a (year, month, day) tuple."""
        return cls(*date_tuple)

d1 = Date(2024, 3, 15)
d2 = Date.from_string("2024-03-15")
d3 = Date.from_tuple((2024, 3, 15))

print(d1)  # Date(2024, 3, 15)
print(d2)  # Date(2024, 3, 15)
print(d3)  # Date(2024, 3, 15)

__init__ permanece simple — solo almacena tres enteros. Los métodos de clase manejan la lógica de conversión. Esto es más limpio que un único __init__ con múltiples parámetros opcionales y ramas if/elif.

Por qué cls importa para la herencia

Cuando un método de clase de fábrica llama a cls(...) en lugar de codificar el nombre de la clase, crea una instancia de cualquier clase sobre la que se llamó el método — incluso una subclase. Por eso siempre debes preferir cls(...) sobre ClassName(...) dentro de un @classmethod.

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

    def __repr__(self):
        return f"{type(self).__name__}({self.year}, {self.month}, {self.day})"

    @classmethod
    def from_string(cls, date_string):
        year, month, day = (int(p) for p in date_string.split("-"))
        return cls(year, month, day)  # uses cls, not Date


class DateTime(Date):
    pass  # inherits from_string


dt = DateTime.from_string("2024-03-15")
print(dt)           # DateTime(2024, 3, 15)  — correct subclass
print(type(dt))     # <class '__main__.DateTime'>

Si from_string hubiera codificado return Date(year, month, day), llamar a DateTime.from_string(...) devolvería un Date, no un DateTime — rompiendo silenciosamente el contrato de herencia.

Modificar el estado a nivel de clase

Los métodos de clase también pueden actuar como constructores con nombre y efectos secundarios, o pueden manipular variables de clase que rastrean el estado compartido:

class Registry:
    _instances = []

    def __init__(self, name):
        self.name = name
        Registry._instances.append(self)

    @classmethod
    def all(cls):
        return list(cls._instances)

    @classmethod
    def clear(cls):
        cls._instances.clear()

Registry("alice")
Registry("bob")
Registry("carol")
print([r.name for r in Registry.all()])  # ['alice', 'bob', 'carol']
Registry.clear()
print(Registry.all())                    # []

Llamar desde una Instancia vs la Clase

Tanto @staticmethod como @classmethod pueden llamarse sobre una instancia o sobre la clase. Python maneja cualquiera de las dos formas:

class Circle:
    PI = 3.14159265

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return Circle.PI * self.radius ** 2

    @classmethod
    def unit_circle(cls):
        """Return a circle with radius 1."""
        return cls(1)

    @staticmethod
    def describe():
        return "A circle is a round plane figure."

c = Circle(5)

# staticmethod — callable on instance or class
print(c.describe())          # A circle is a round plane figure.
print(Circle.describe())     # A circle is a round plane figure.

# classmethod — callable on instance or class
unit = c.unit_circle()
print(unit.radius)           # 1
print(Circle.unit_circle().radius)  # 1

Llamarlos sobre la clase suele ser más claro — indica al lector que no hay datos de instancia involucrados.

Combinar @classmethod y @staticmethod

Un método de clase puede delegar el trabajo de validación a un método estático, porque el método de clase tiene acceso a cls para llamarlo:

class PositiveNumber:
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f"PositiveNumber({self.value})"

    @staticmethod
    def _validate(value):
        if value <= 0:
            raise ValueError(f"Expected a positive number, got {value!r}")

    @classmethod
    def create(cls, value):
        cls._validate(value)
        return cls(value)

n = PositiveNumber.create(42)
print(n)  # PositiveNumber(42)

try:
    PositiveNumber.create(-5)
except ValueError as e:
    print(e)  # Expected a positive number, got -5

Referencia Rápida: ¿Qué Decorador Debo Usar?

SituaciónRecomendación
El método lee o escribe selfMétodo de instancia normal
El método crea una nueva instancia@classmethod (fábrica / constructor alternativo)
El método lee o escribe un atributo de clase@classmethod
El método es un auxiliar puro que no necesita datos de clase o instancia@staticmethod (o función a nivel de módulo)
El método valida la entrada antes de la construcción@staticmethod
El método debe funcionar correctamente en subclases@classmethod (usa cls, no el nombre de clase codificado)

Errores Comunes

Olvidar cls dentro de @classmethod

Si codificas el nombre de la clase en lugar de usar cls, la herencia se rompe silenciosamente:

class Animal:
    @classmethod
    def create(cls):
        return cls()          # correct — returns an instance of the actual class

class Dog(Animal):
    pass

print(type(Dog.create()))     # <class '__main__.Dog'>  — correct

Siempre usa cls(...), nunca Animal(...), dentro de un método de clase.

Acceder a self o cls en un @staticmethod

Un @staticmethod no recibe ningún primer argumento implícito. Intentar referenciar self o cls dentro de él es un error:

class Bad:
    label = "bad"

    @staticmethod
    def show():
        # print(cls.label)  # NameError: name 'cls' is not defined
        print("use @classmethod if you need cls")

Bad.show()  # use @classmethod if you need cls

Si te encuentras necesitando cls en lo que pensabas que era un método estático, cámbialo a un @classmethod.

Confundir los decoradores

Los métodos @classmethod deben tener cls como primer parámetro explícito, y los métodos @staticmethod deben tener ninguno. Intercambiarlos provoca un TypeError en el momento de la llamada, no en el momento de la definición — lo que puede ser sorprendente:

class Broken:
    @staticmethod
    def forgot_cls(cls):   # cls is just a regular positional argument here
        return cls

# Broken.forgot_cls()  # TypeError: forgot_cls() missing 1 required positional argument: 'cls'

Sobreescribir en subclases

Ambos decoradores funcionan con super() y pueden ser sobreescritos:

class Base:
    @classmethod
    def who(cls):
        return f"Base.who called with cls={cls.__name__}"

class Child(Base):
    @classmethod
    def who(cls):
        parent = super().who()
        return f"Child.who — parent said: {parent}"

print(Child.who())
# Child.who — parent said: Base.who called with cls=Child

Observa que cls en Base.who sigue siendo Child — porque el método fue despachado desde Child.

Ejemplo del Mundo Real: Una Clase User

Aquí tienes un ejemplo completo que reúne métodos de instancia, un método de clase de fábrica y un validador de método estático:

import re

class User:
    _all_users = []

    def __init__(self, name, email):
        User._validate_email(email)
        self.name = name
        self.email = email
        User._all_users.append(self)

    def __repr__(self):
        return f"User(name={self.name!r}, email={self.email!r})"

    # --- instance method ---
    def greet(self):
        return f"Hello, my name is {self.name}."

    # --- factory / alternative constructor ---
    @classmethod
    def from_dict(cls, data):
        """Create a User from a dict like {'name': 'Alice', 'email': '[email protected]'}."""
        return cls(data["name"], data["email"])

    # --- class-level query ---
    @classmethod
    def count(cls):
        return len(cls._all_users)

    # --- pure helper, no instance or class data needed ---
    @staticmethod
    def _validate_email(email):
        pattern = r"^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$"
        if not re.match(pattern, email):
            raise ValueError(f"Invalid email address: {email!r}")

# Create via normal constructor
u1 = User("Alice", "[email protected]")

# Create via factory
u2 = User.from_dict({"name": "Bob", "email": "[email protected]"})

print(u1.greet())    # Hello, my name is Alice.
print(u2.greet())    # Hello, my name is Bob.
print(User.count())  # 2

try:
    User("Carol", "not-an-email")
except ValueError as e:
    print(e)         # Invalid email address: 'not-an-email'

Este patrón — __init__ para la construcción normal, @classmethod para constructores alternativos, @staticmethod para auxiliares — aparece en toda la biblioteca estándar de Python (véase datetime.date.today(), datetime.date.fromisoformat(), int.from_bytes()).

Resumen

  • Un método de instancia recibe self y tiene acceso completo al estado del objeto.
  • Un @classmethod recibe cls — la clase en sí — en lugar de una instancia. Úsalo para métodos de fábrica y todo lo que opere sobre el estado a nivel de clase. Siempre usa cls(...) dentro de él para que las subclases funcionen correctamente.
  • Un @staticmethod no recibe ni self ni cls. Úsalo para lógica de utilidad pura que pertenece al espacio de nombres de la clase pero no necesita datos del objeto ni de la clase.

Para atributos calculados que parecen acceso a atributos normales, consulta @property. Para el mecanismo completo de decoradores que hace funcionar los tres, consulta Decoradores de Python.

Was this page helpful?