Un argumento por defecto se evalúa una sola vez, cuando se define la función. Si ese valor es una lista o un diccionario, todas las llamadas comparten el mismo objeto.
Asignar una lista a un segundo nombre no copia nada. Copiar tiene dos niveles, y el superficial se detiene justo donde suelen estar tus datos anidados.
== pregunta si dos objetos tienen el mismo valor. is pregunta si son el mismo objeto en memoria. Coinciden lo bastante seguido como para que usar el equivocado casi siempre funcione.
Un decorador es una función que toma una función y devuelve un reemplazo. La línea con @ es la forma corta de reasignar el nombre.
Un asterisco delante de un parámetro junta los argumentos posicionales que sobran en una tupla. Dos asteriscos juntan los argumentos por palabra clave que sobran en un diccionario.
Una comprensión construye una lista a partir de un bucle en una sola expresión. No es solo más corta: también es más rápida que ir agregando dentro de un bucle.
Una f-string evalúa la expresión entre llaves y mete el resultado en el texto. Es más rápida que % y .format(), y se lee en el orden en que piensas.
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.
__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.
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.