Los métodos de instancia reciben self, los de clase reciben cls y los estáticos no reciben ninguno automáticamente. La elección refleja la dependencia real del comportamiento.
Un constructor alternativo
from dataclasses import dataclass
from datetime import date
@dataclass(frozen=True)
class Suscripcion:
inicio: date
@classmethod
def desde_iso(cls, valor: str) -> "Suscripcion":
return cls(inicio=date.fromisoformat(valor))
@staticmethod
def formato_aceptado() -> str:
return "AAAA-MM-DD"
Usar cls(...) preserva subclases, a diferencia de escribir Suscripcion(...). staticmethod encaja cuando una operación pertenece conceptualmente a la API sin necesitar estado.
Criterio práctico
Usa método de instancia para comportamiento del objeto. Usa classmethod para fábricas que construyen la clase concreta o leen configuración de clase. Considera una función de módulo antes que staticmethod si varios tipos reutilizan la lógica.
No ocultes dependencias globales con decoradores. Pasa colaboradores explícitamente. Consulta dataclasses en Python y programación orientada a objetos.
Cómo funciona el binding
Las funciones definidas en una clase implementan el protocolo descriptor. Al acceder a un método de instancia mediante un objeto, Python crea un método vinculado y proporciona el objeto primero. Al acceder mediante la clase, debes pasar la instancia:
class Contador:
def incrementar(self, valor: int) -> int:
return valor + 1
contador = Contador()
assert contador.incrementar(2) == 3
assert Contador.incrementar(contador, 2) == 3
classmethod cambia el binding para proporcionar la clase usada en el acceso. staticmethod lo desactiva y devuelve la función sin añadir argumentos. self y cls son convenciones, no palabras reservadas, pero respetarlas aclara la intención.
classmethod y herencia
La ventaja de cls aparece con subclases. Una fábrica debe construir el tipo por el que fue invocada:
@dataclass(frozen=True)
class SuscripcionAnual(Suscripcion):
descuento: int = 10
plan = SuscripcionAnual.desde_iso("2026-09-01")
assert isinstance(plan, SuscripcionAnual)
Funciona porque el método llama a cls(...). Escribir Suscripcion(...) devolvería siempre la base. La fábrica debe seguir siendo compatible con los constructores derivados. Añadir campos obligatorios incompatibles puede romperla. Sobrescribe la fábrica, ofrece valores adecuados o usa un servicio externo si los constructores divergen.
Los métodos de clase también leen atributos de la clase concreta:
class Importador:
separador = ","
@classmethod
def separar(cls, linea: str) -> list[str]:
return linea.split(cls.separador)
class ImportadorTsv(Importador):
separador = "\t"
ImportadorTsv.separar(...) usa tabulaciones sin duplicar lógica. Esto sirve para configuración estable por subtipo. Los valores que cambian por entorno o petición deben inyectarse, no almacenarse como estado global mutable.
Constructores alternativos bien diseñados
Un nombre descriptivo indica la representación aceptada: desde_json, desde_fila_csv o desde_configuracion. La fábrica debe validar la entrada y devolver un objeto válido. Si realiza I/O, caché, red o demasiada coordinación, una función o servicio separado suele tener una responsabilidad más clara.
Anota el retorno con el tipo actual. En Python moderno, typing.Self expresa que el resultado acompaña a la clase concreta:
from typing import Self
class Usuario:
def __init__(self, nombre: str) -> None:
self.nombre = nombre
@classmethod
def desde_texto(cls, texto: str) -> Self:
nombre = texto.strip()
if not nombre:
raise ValueError("nombre vacío")
return cls(nombre)
Para versiones anteriores a Python 3.11, usa una variable limitada o referencia textual según la compatibilidad del proyecto. La anotación no sustituye la validación.
Cuándo encaja staticmethod
Un método estático puede mantener cerca del tipo una operación pequeña que pertenece claramente a su vocabulario, como validar un formato exclusivo. También evita el binding accidental de una función guardada en el cuerpo de la clase.
class CodigoProducto:
@staticmethod
def normalizar(valor: str) -> str:
return valor.strip().upper()
Si normalizar empieza a ser útil para clientes, pedidos e importadores, moverla a un módulo compartido hace explícita su reutilización. staticmethod no da acceso especial a atributos privados, no es más rápido por naturaleza y no aísla estado.
Sobrescritura y super()
Los métodos de instancia y clase participan naturalmente en el polimorfismo. Un método de clase sobrescrito recibe la subclase y puede delegar mediante super(). Los estáticos también se sobrescriben, pero una llamada con un nombre de clase fijo puede saltarse la versión derivada. Si la variación polimórfica importa, un método de clase o instancia suele expresarlo mejor.
Evita cambiar el tipo de método al sobrescribir, por ejemplo sustituir uno de instancia por uno estático. Aunque algunas llamadas funcionen, el contrato resulta sorprendente para lectores, subclases y verificadores.
Pruebas y errores frecuentes
Prueba las fábricas con entradas válidas, límites y fallos de parsing. Incluye una subclase cuando conservar herencia sea parte del contrato. Para métodos estáticos puros, prueba entrada y salida igual que con una función.
No uses classmethod como disfraz de estado global mutable. Cambiar atributos de clase en pruebas puede filtrarse a otros casos y provocar problemas de concurrencia. Tampoco elijas staticmethod solo porque el cuerpo actual no usa self; quizá represente comportamiento del objeto y luego necesite estado, o quizá deba ser función de módulo. Decide por la responsabilidad pública.
Inspección y llamadas equivalentes
Observar los atributos refuerza la diferencia. Suscripcion.desde_iso ya es un método vinculado a la clase, mientras Suscripcion.formato_aceptado es la función estática expuesta por su namespace. En el diccionario bruto de la clase, antes de actuar el descriptor, los valores son objetos classmethod y staticmethod.
Es válido llamar un método estático mediante una instancia, pero suele ocultar su independencia. Prefiere CodigoProducto.normalizar(valor) para mostrar que no participa ningún objeto. También puedes llamar un método de clase por una instancia, aunque Tipo.fabrica(...) comunica mejor que se construirá un objeto nuevo.
El orden de decoradores importa al combinarlos. @classmethod envuelve la función y normalmente aparece como decorador externo. Evita combinaciones ingeniosas con property; las API explícitas y compatibles con verificadores son más mantenibles.
Antes de elegir, pregunta: ¿el resultado depende del objeto receptor, de la clase receptora o de ninguno? Las respuestas apuntan al método de instancia, classmethod o función independiente. Reserva staticmethod para casos donde el namespace de la clase aporte significado real.
Revisa también las llamadas desde la perspectiva del lector. La opción correcta debería poder entenderse sin abrir la implementación y debería conservar ese significado al crear subclases.
La documentación oficial de classmethod y staticmethod, consultada el 22 de julio de 2026, explica binding y herencia. Una API pequeña y predecible importa más que guardar toda función relacionada dentro de una clase.