La mayoría de las clases en Python debieron ser funciones. La prueba no es «¿esto es un sustantivo?», sino si existe un estado inválido que la clase pueda impedir.
self es el nombre de un parámetro, no una palabra reservada. __init__ no crea el objeto. Y un atributo de clase es compartido hasta el momento en que asignas a través de una instancia.
Una clase sin __repr__ te hace perder tiempo desde el momento en que existe. repr es para ti, str es para quien lee, y solo uno de los dos tiene un respaldo razonable.
Python no necesita getters ni setters, porque @property te deja agregar validación después sin cambiar ni una sola llamada.
La pregunta siempre es solo qué necesita el método: esta instancia, la clase, o ninguna de las dos. Equivocarse rompe las subclases sin avisar.
La herencia le da a una subclase todo, incluso lo que no debería tener. El problema del pingüino, y qué hacer en su lugar.
Cada operador y cada función incorporada de Python le está haciendo una pregunta a tu objeto. Contéstala y tu clase se comporta como un tipo nativo.
Cuatro formas de guardar datos estructurados en Python. La pregunta que decide no es de estilo: es si un error de tipeo debería fallar en voz alta.
asdict() convierte una dataclass en un diccionario y baja recursivamente hasta el fondo, copiando por el camino. Normalmente es lo que quieres, y de vez en cuando es exactamente lo que no.
El duck typing falla en el lugar de la llamada, mucho después del error. Las ABCs y los Protocols arreglan eso, en direcciones opuestas.
__slots__ elimina el diccionario por instancia. Ahorra memoria de verdad a gran escala, atrapa errores de tipeo gratis, y es una mala opción por defecto.
Cada operador, cada función incorporada y cada pieza de sintaxis en Python es una pregunta que se le hace a tu objeto. El modelo de datos es la lista de preguntas.