Buenas Prácticas de Python Orientado a Objetos
Python no es Java: inclínate hacia objetos simples, funciones y protocolos en lugar de jerarquías de herencia profundas. Estas reglas mantienen el código OO "pythónico" y testeable.
Cómo Usar Esta Lista
- Aplica en revisiones de diseño para nuevos modelos de dominio.
- Usa al refactorizar "god-classes" o "mixin soup".
- Combina con protocolos
mypyy Pydantic en los bordes del sistema. - Revisa después de incidentes causados por estado de clase mutable compartido.
A - Modelado
- Usa
@dataclasspara registros de datos; añade métodos solo cuando el comportamiento sea intrínseco. No todos los diccionarios necesitan una clase. - Prefiere la composición (
tiene-un) sobre la herencia (es-un) más allá de un nivel. Inyecta colaboradores en__init__. - Usa
Enum/StrEnumen lugar de constantes de cadena.matchexhaustivo y prevención de errores tipográficos. - Modela dinero, IDs y cantidades como pequeños tipos de valor. No enteros/cadenas desnudas a través de capas.
- Mantén las clases enfocadas: una razón para cambiar. Divide modelos de lectura de servicios de escritura cuando sea necesario.
B - Herencia y APIs
- Limita los mixins a una preocupación (registro, serialización). Documenta el MRO al usar múltiples.
- Llama a
super().__init__en cadenas de herencia múltiple cooperativas. Las cadenas rotas omiten la inicialización. - Prefiere
Protocolo ABC solo cuando existan implementaciones múltiples. YAGNI para implementación única. - Usa fábricas
@classmethodpara constructores alternativos. No patrones de__init__sobrecargados. - API pública pequeña; prefija internos con
_. Las propiedades exponen atributos estables sobre campos brutos.
C - Corrección
- Alinea
__eq__y__hash__para objetos de valor usados en conjuntos/diccionarios. O usa una clasedataclassconfrozen=True. - Nunca mutes objetos congelados o supuestamente inmutables. Los campos de tupla dentro de una clase
dataclasssiguen siendo mutables. - Valida invariantes en
__post_init__o setters. Falla rápido en la construcción. - Devuelve
NotImplementedde__eq__para tipos desconocidos. Deja que el otro operando intente la comparación inversa. - Evita atributos de clase mutables como valores predeterminados compartidos. Establece por instancia en
__init__.
D - Disciplina de Metaprogramación
- Por defecto, usa
__init_subclass__para hooks de registro. No metaclasses personalizadas. - Recurre a descriptores/propiedades solo cuando la validación o el cálculo perezoso se repitan. De lo contrario, usa
__init__simple. - Usa
__slots__solo con presión de memoria medida. Acepta las restricciones de herencia. - Documenta los registros de plugins y límpialos en las pruebas. Previene la contaminación entre pruebas.
- Omite la sobrecarga de operadores a menos que el dominio sea naturalmente numérico/vectorial. Un
+sorprendente perjudica a los lectores.
E - Pruebas y Límites
- Inyecta dependencias (reloj, almacenamiento, notificador) en los constructores. No globales codificados.
- Implementaciones falsas para puertos ABC/Protocol en pruebas unitarias. Sin red en pruebas de dominio.
- Usa Pydantic 2 en los límites HTTP; clases
dataclasssimples en el interior. Separación de transporte y dominio. - Prefiere funciones para transformaciones sin estado entre objetos enriquecidos. Métodos cuando el comportamiento pertenece a los datos.
- Mantén las vistas de Django/FastAPI delgadas: la lógica de dominio en servicios testeables.
Preguntas Frecuentes
¿Cuándo está bien usar herencia?
Puntos de extensión de frameworks, mixins pequeños, polimorfismo de subtipos real con pocos niveles.
¿Clase `dataclass` vs modelo Pydantic?
Clase dataclass en el núcleo del dominio; Pydantic donde se requiera validación de JSON/esquema.
¿Cada archivo debe tener clases?
No, los módulos de funciones son "pythónicos". Clases cuando el estado + comportamiento se agrupan de forma natural.
¿Protocolo o ABC para puertos?
Protocolo para tipos de terceros estructurales; ABC cuando controlas la herencia y quieres guardas de instanciación.
¿Cuántos métodos por clase?
Sin máximo fijo: si la clase se desplaza sin fin, divide por responsabilidad o extrae ayudantes.
¿Privado con doble guion bajo?
El __name mangling es raro en código de aplicación; la convención de un solo _ es suficiente.
¿Heredar de tipos incorporados?
class MyDict(dict) con moderación: a menudo se envuelve o compone en su lugar para APIs más claras.
¿Modelos ORM como dominio?
Mantén las filas ORM anémicas separadas del dominio enriquecido cuando la complejidad crezca: capa anti-corrupción.
¿Cómo probar clases `dataclass`?
Construye con campos mínimos; usa replace() para variantes congeladas en pruebas.
¿El mayor "olor" OO en Python?
Herencia profunda que reemplaza funciones simples: refactoriza a composición y protocolos.
Relacionados
- Conceptos básicos de OOP - ejemplos introductorios
- Herencia y MRO -
supery mixins - Protocolos y Tipado Estructural - interfaces
- Clases
dataclass- patrón de registro
Versiones de pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.