Read in other languages: English 🇺🇸, Polska 🇵🇱, German 🇩🇪, French 🇫🇷, Spanish 🇪🇸, Українська 🇺🇦.
1. ¿Qué es Python y cuáles son sus características principales?
Python es un lenguaje de alto nivel y de propósito general, enfocado en la legibilidad, el desarrollo rápido y una amplia biblioteca estándar.
Características principales:
- Sintaxis simple: el código es fácil de leer y mantener.
- Modelo de ejecución interpretado: ciclo rápido de cambios sin una fase de compilación manual.
- Multiplataforma: el mismo código funciona en Linux, macOS y Windows.
- Multiparadigma: enfoques procedimental, POO y funcional en un mismo proyecto.
- Ecosistema sólido:
pip,venv,pyproject.tomly miles de bibliotecas listas para usar. - Funciones modernas de Python 3.10+: anotaciones de tipos,
dataclass,async/await,match/case, generadores y gestores de contexto.
Ejemplo de una función con estilo de producción:
from dataclasses import dataclass
@dataclass(slots=True)
class User:
name: str
active: bool
def process_users(users: list[User]) -> list[str]:
return [user.name for user in users if user.active]En resumen:
- Python acelera el desarrollo gracias a su sintaxis simple y a su gran biblioteca estándar.
- El lenguaje sirve para back-end, automatización, datos/ML, pruebas y DevOps.
- En Python 3.10+, las prácticas clave son las anotaciones de tipos, el I/O asíncrono y una biblioteca estándar moderna.
2. ¿Cuáles son las ventajas clave de Python?
Ventajas clave de Python en producción:
- alta velocidad de desarrollo gracias a su sintaxis concisa;
- gran biblioteca estándar (
pathlib,itertools,collections,asyncio); - ecosistema maduro de paquetes para back-end, datos, automatización y pruebas;
- portabilidad del código entre distintos sistemas operativos;
- buen soporte para tipado (
typing,mypy,pyright) y prácticas modernas.
En resumen:
- Python reduce el tiempo de salida al mercado.
- Proporciona muchas herramientas listas para usar sin dependencias adicionales.
- Sirve tanto para MVP como para servicios escalables.
3. ¿En qué se diferencia Python de los lenguajes compilados?
Python normalmente se ejecuta mediante un intérprete: el código se compila a bytecode y se ejecuta en tiempo de ejecución en la VM de CPython. En los lenguajes compilados como C/C++ o Rust, normalmente hay una compilación previa a código máquina.
Consecuencias prácticas:
- Python es más rápido para desarrollo y prototipado.
- Los lenguajes compilados nativos suelen ser más rápidos en tareas intensivas de CPU.
- En Python, el rendimiento suele mejorarse con algoritmos, perfilado y extensiones en C.
En resumen:
- Python optimiza la velocidad de desarrollo.
- La compilación a código máquina normalmente ofrece mejor rendimiento bruto.
- La elección depende del dominio y de los requisitos de latencia y rendimiento.
4. ¿Qué significa el tipado dinámico en Python?
El tipado dinámico significa que el tipo está asociado al objeto, no al nombre de la variable. Un nombre puede referirse a valores de distintos tipos en diferentes momentos de la ejecución.
value = 10 # int
value = "10" # strLos tipos se comprueban durante la ejecución, por lo que los errores de tipos aparecen en tiempo de ejecución si no se usa un analizador estático de tipos.
En resumen:
- En Python, el tipo pertenece al objeto, no a la variable.
- El tipo puede cambiar cuando se reasigna el nombre.
- Las anotaciones de tipos añaden control antes de ejecutar el código.
5. ¿Qué significa strong typing en Python?
Strong typing en Python significa que el intérprete no realiza conversiones implícitas peligrosas entre tipos incompatibles.
1 + "2" # TypeErrorPara operar entre tipos distintos se requiere una conversión explícita:
1 + int("2") # 3En resumen:
- Python tiene tipado dinámico, pero es estricto respecto a la compatibilidad de tipos.
- Las conversiones implícitas peligrosas se bloquean con un error.
- La conversión explícita hace que el comportamiento sea predecible.
6. ¿Qué es el REPL y cuándo se utiliza?
El REPL (Read-Eval-Print Loop) es un modo interactivo en el que introduces una expresión, obtienes el resultado de inmediato y validas hipótesis rápidamente.
Casos de uso:
- comprobar la API de una biblioteca;
- probar rápidamente una expresión o un algoritmo;
- explorar datos antes de implementarlos en un módulo.
Herramientas: python estándar, ipython, ptpython.
En resumen:
- El REPL ofrece el ciclo de retroalimentación más rápido.
- Es útil para experimentos y para depurar.
- No sustituye a las pruebas, pero reduce el tiempo para validar ideas.
7. ¿Qué son los literals en Python? Da ejemplos de distintos tipos.
Un literal es un valor fijo escrito directamente en el código.
name = "Ada" # str literal
count = 42 # int literal
ratio = 3.14 # float literal
enabled = True # bool literal
items = [1, 2, 3] # list literal
config = {"retries": 3} # dict literal
flags = {"a", "b"} # set literal
point = (10, 20) # tuple literal
raw = b"abc" # bytes literalEn resumen:
- Los literals son valores constantes incorporados en el código.
- Cada tipo básico tiene su propia sintaxis literal.
- Se usan con frecuencia para inicializar datos.
8. ¿Qué son las keywords en Python?
Las keywords son palabras reservadas del lenguaje con un propósito sintáctico especial y no pueden usarse como nombres de variables.
Ejemplos: if, for, class, def, match, case, try, except,
async, await.
Para ver la lista:
import keyword
print(keyword.kwlist)En resumen:
- Las keywords definen la sintaxis de Python.
- No pueden usarse como identificadores.
- La lista está disponible a través del módulo
keyword.
9. ¿Qué es una variable en Python?
Una variable en Python es un nombre, una referencia, que apunta a un objeto en memoria. La asignación vincula el nombre con el objeto, no "mete el valor en una caja".
x = [1, 2]
y = x
y.append(3)
# x e y apuntan a la misma listaEn resumen:
- Una variable es una referencia a un objeto.
- Varios nombres pueden apuntar al mismo objeto mutable.
- Esto es importante para entender las copias y los efectos secundarios.
10. ¿Qué son `pass` y `...` (Ellipsis) en Python?
pass es una instrucción vacía: no hace nada, pero permite definir un bloque
vacío sintácticamente correcto.
... (Ellipsis) es un objeto separado, Ellipsis. A menudo se usa como marca
de "aún no implementado", en anotaciones de tipos y en bibliotecas como NumPy.
def feature() -> None:
pass
PLACEHOLDER = ...En resumen:
passse necesita para bloques de código vacíos....es un valor-objeto, no una keyword.- Ambos se usan como marcadores temporales, pero con distinta semántica.
11. ¿Qué es PEP y cómo influye en la evolución de Python?
PEP (Python Enhancement Proposal) es un documento oficial que describe una nueva funcionalidad, un estándar o un proceso dentro del ecosistema de Python.
A través de un PEP pasan:
- discusión de diseño;
- argumentación técnica;
- decisión de aceptación o rechazo;
- estandarización de prácticas.
Ejemplos: PEP 8 (estilo), PEP 484 (anotaciones de tipos), PEP 634 (coincidencia estructural de patrones).
En resumen:
- PEP es el mecanismo de evolución del lenguaje y de sus herramientas.
- Hace que los cambios sean transparentes y formalizados.
- Muchas prácticas modernas de Python quedaron fijadas precisamente a través de los PEP.
12. ¿Qué es PEP 8 y para qué sirve?
PEP 8 es la guía oficial de estilo para código Python: nombres, formato, indentación, importaciones, longitud de líneas y legibilidad de construcciones.
Para qué sirve:
- el código mantiene un estilo uniforme en el equipo;
- el code review es más rápido;
- hay menos ruido en el diff;
- aumenta la mantenibilidad.
En la práctica, el estilo se automatiza con ruff format o con black + ruff.
En resumen:
- PEP 8 estandariza el estilo del código Python.
- Trata de legibilidad y mantenimiento, no de rendimiento.
- Su cumplimiento se automatiza mejor con herramientas.
13. ¿Qué funcionalidades nuevas aparecieron en las versiones modernas de Python 3.10+?
Funcionalidades clave de Python moderno:
match/case(coincidencia estructural de patrones);- mejoras en la sintaxis y en los mensajes de error;
- union types con
|(int | None); typing.Self,typing.TypeAlias,typing.override;- generics al estilo de PEP 695 (
class Box[T]: ...,def f[T](...) -> ...); tomlliben la biblioteca estándar.
En la práctica, esto reduce el código repetitivo y mejora la fiabilidad del código tipado.
En resumen:
- Python 3.10+ mejoró de forma notable la sintaxis y el typing.
match/casey eltypingmoderno son lo que más impacta el código.- Las nuevas features conviene usarlas por defecto en código nuevo.
14. ¿Qué es un docstring en Python?
Un docstring es una cadena de documentación, la primera expresión dentro de un
módulo, clase o función. Está disponible mediante __doc__ y la usan IDEs,
help() y generadores de documentación.
def normalize_email(email: str) -> str:
"""Return lowercase email without surrounding spaces."""
return email.strip().lower()Se recomienda describir qué hace la función, sus argumentos, el valor de retorno y las excepciones.
En resumen:
- El docstring es documentación integrada en el código.
- Sirve como fuente para
help()y para la generación automática de docs. - Un buen docstring reduce la necesidad de leer la implementación.
15. ¿Cómo funcionan las indentaciones en Python?
En Python, la indentación define los bloques de código, como el cuerpo de
if, for, def o class. Es decir, la indentación es parte de la sintaxis,
no solo del estilo.
if is_ready:
run_task()
else:
stop_task()Mezclar tabs y spaces puede provocar IndentationError o una estructura de
bloque incorrecta.
En resumen:
- La indentación en Python define la estructura del programa.
- Una indentación incorrecta rompe la ejecución.
- Usa un estilo consistente: 4 espacios.
16. ¿Cuántos espacios deben usarse para la indentación según PEP 8 y por qué es importante mantenerla consistente?
PEP 8 recomienda 4 espacios por cada nivel de indentación.
Por qué es importante:
- estructura de bloques uniforme en todo el proyecto;
- apariencia predecible en distintos editores;
- menos errores por conflictos entre tab y space;
- diffs más limpios en Git.
En resumen:
- La indentación estándar es de 4 espacios.
- Un estilo único elimina toda una clase de errores de formato.
- Esto mejora directamente la legibilidad y el mantenimiento del código.
17. ¿Cuál es la diferencia entre `ruff` y `black` en funcionalidad y uso?
black es un formateador de código con reglas fijas.
ruff es un linter rápido, además de formateador y autofixer de muchas reglas
como PEP 8, imports, bugs potenciales y simplificaciones de sintaxis.
En la práctica:
- o bien
ruff check --fix+ruff format; - o bien
ruffpara análisis estático +blackpara formateo.
En resumen:
blackse centra en el formateo.ruffcubre el linting y parte de las autocorrecciones.- En proyectos modernos, a menudo basta con solo
ruff.
18. Explica el propósito de las anotaciones de tipos en Python y muestra un ejemplo de función con anotaciones de tipos.
Las anotaciones de tipos describen los tipos esperados de los argumentos y del valor de retorno. Mejoran el autocompletado, el análisis estático y la legibilidad de la API.
def process_users(users: list[dict[str, object]]) -> list[str]:
return [str(user["name"]) for user in users if bool(user.get("active"))]Las anotaciones no realizan validación en tiempo de ejecución por sí mismas, pero ayudan a
detectar errores en CI mediante mypy o pyright.
En resumen:
- Las anotaciones de tipos documentan el contrato de la función.
- Permiten detectar errores antes mediante comprobación estática.
- Mejoran la calidad de la API en bases de código grandes.
19. ¿Qué es la depuración y por qué es una habilidad importante para los programadores?
La depuración es la búsqueda sistemática de la causa de un comportamiento incorrecto del código y su corrección. No es solo "encontrar un bug", sino reproducirlo, localizarlo y verificar la corrección.
Ciclo básico:
- reproducir el problema;
- reducir la zona de código;
- comprobar la hipótesis con logs o depurador;
- añadir un test que fije el escenario.
En resumen:
- La depuración reduce el MTTR y la cantidad de regresiones.
- Funciona mejor como proceso, no como búsqueda caótica.
- El cierre correcto de la depuración es: corrección + test + verificación posterior.
20. Explica el propósito de usar `print` para depurar. Da un ejemplo de una situación donde este enfoque sea útil.
La depuración con print es una forma rápida de comprobar valores de variables y el
orden de ejecución sin lanzar herramientas más complejas.
Es útil cuando necesitas validar rápidamente una hipótesis en un script local:
def parse_port(raw: str) -> int:
value = raw.strip()
print(f"raw={raw!r} value={value!r}")
return int(value)Para código de producción, es mejor pasar a logging, para disponer de niveles de
log y una salida controlada.
En resumen:
printsirve para un diagnóstico local corto.- Muestra rápidamente el estado de las variables en el punto problemático.
- Para un diagnóstico estable en un servicio, usa
logging.
21. Describe cómo usar Python Debugger (PDB) para depurar. ¿Qué ventajas tiene PDB frente a `print`?
pdb permite detener la ejecución del programa, avanzar línea por línea, ver
el estado de las variables y la pila de llamadas.
Uso básico:
import pdb
def compute(x: int) -> int:
pdb.set_trace()
return x * 2Comandos clave: n (next), s (step), c (continue), p expr (print expr),
l (list), bt (backtrace).
En resumen:
pdbda control interactivo sobre la ejecución.- Es más eficaz que
printpara escenarios complejos. - Permite diagnosticar el estado sin ensuciar el código con logs.
22. ¿Qué estrategias se pueden aplicar para depurar bucles y funciones en Python?
Estrategias prácticas:
- aislar el error con un ejemplo mínimo reproducible;
- registrar invariantes en las iteraciones del bucle;
- poner breakpoints en la entrada y salida de la función;
- comprobar casos límite como datos vacíos,
Noneo volúmenes grandes; - cubrir la ruta problemática con un test.
En los bucles, es útil registrar el índice y los estados intermedios clave.
En resumen:
- Empieza reproduciendo y acotando el problema.
- Comprueba invariantes y condiciones límite.
- Cierra el fix con un test que detecte la regresión.
23. ¿Qué impacto tienen las funciones de larga duración en el rendimiento del programa?
Las funciones de larga duración aumentan la latencia y bloquean procesos de trabajo o hilos y empeoran el rendimiento del servicio.
Riesgos:
- respuestas lentas de la API;
- tiempos de espera;
- crecimiento de las colas de tareas;
- peor uso de CPU e I/O.
Enfoque: perfilar con cProfile, dividir en pasos más pequeños, usar
generadores o procesamiento en flujo y mover los cálculos pesados a multiprocessing o a
tareas en segundo plano.
En resumen:
- Las funciones largas golpean directamente la latencia.
- La optimización debe basarse en perfilado, no en intuición.
- A nivel de arquitectura, conviene separar el trabajo intensivo de CPU del intensivo de I/O.
24. ¿Qué es logging y por qué es mejor usarlo en lugar de `print`?
logging es el mecanismo estándar para registrar eventos con niveles como
DEBUG, INFO, WARNING, ERROR y CRITICAL, además de formatos y handlers
como consola y archivo.
Por qué es mejor que print:
- niveles de detalle controlables;
- formato estructurado;
- configuración centralizada;
- posibilidad de integración con un stack de observability.
En resumen:
loggingsirve para producción;print, no.- Los niveles de log permiten controlar el ruido.
- Los logs deben ser estructurados y consistentes.
25. ¿Qué es un traceback?
Un traceback es el stack de llamadas que Python muestra cuando ocurre una excepción: dónde empezó la ejecución, por qué funciones pasó el código y en qué punto exacto ocurrió el error.
Uso:
- encontrar rápidamente el archivo y la línea del origen del error;
- entender el camino de ejecución hasta el fallo;
- construir un test preciso para reproducirlo.
En resumen:
- El traceback es la "ruta" hacia el error.
- La parte más valiosa son los últimos frames del stack y el tipo de excepción.
- Analizar el traceback acelera el root cause analysis.
26. ¿Cuáles son los tipos de datos principales en Python?
Tipos built-in principales:
- numéricos:
int,float,complex; - lógico:
bool; - cadenas y bytes:
str,bytes,bytearray; - colecciones:
list,tuple,set,dict,frozenset; - especiales:
NoneType(None).
Es importante entender la mutability:
- mutable:
list,dict,set,bytearray; - immutable:
int,str,tuple,frozenset.
En resumen:
- Python incluye un conjunto rico de tipos básicos "out of the box".
- La elección de la estructura de datos afecta la complejidad de las operaciones.
- La mutability define el comportamiento de las copias y de los efectos secundarios.
27. ¿Cuál es la diferencia entre la división normal (`/`) y la división entera (`//`) en Python?
/ realiza una división normal y siempre devuelve float.
// realiza una floor division: devuelve la parte entera hacia abajo, hasta
-∞, y normalmente el tipo es int cuando los operandos son enteros.
7 / 2 # 3.5
7 // 2 # 3
-7 // 2 # -4En resumen:
/devuelve un resultado decimal.//redondea hacia abajo hasta un entero.- Para números negativos,
//no equivale a "truncar hacia cero".
28. Explica el uso del operador módulo (`%`) en Python. ¿Cómo se usa para saber si un número es par o impar?
El operador % devuelve el resto de una división.
Comprobación de paridad:
def is_even(n: int) -> bool:
return n % 2 == 0Si n % 2 == 0, el número es par; en caso contrario, es impar.
Casos típicos: índices cíclicos, partición en grupos y cálculos de calendario.
En resumen:
%devuelve el resto.n % 2es la comprobación estándar de paridad.- Se usa a menudo en lógica cíclica.
29. ¿Cómo trabaja Python con enteros grandes y qué límites existen al trabajar con números muy grandes?
int en Python tiene precisión arbitraria, es decir, no está limitado a
32/64 bits como en muchos otros lenguajes.
Los límites son prácticos:
- memoria del proceso;
- tiempo de cálculo con valores muy grandes;
- el coste de las operaciones crece con el número de dígitos.
En resumen:
- No existe overflow de
inten el sentido tradicional. - El límite lo ponen los recursos de la máquina, no un tamaño fijo en bits.
- Para cálculos grandes, importan mucho los algoritmos y el perfilado.
30. ¿Qué importancia tiene manejar la división entre cero en Python y cómo prevenir `ZeroDivisionError` en el código?
Dividir entre cero provoca ZeroDivisionError. Debe manejarse de forma
explícita si el divisor llega desde entrada externa o se calcula dinámicamente.
def safe_div(a: float, b: float) -> float | None:
if b == 0:
return None
return a / bEn escenarios críticos, usa try/except y registra el contexto.
En resumen:
- Dividir entre cero es un error de ejecución controlado.
- La prevención más simple es comprobar el divisor antes de operar.
- Con datos externos, añade protección y diagnóstico.
31. Describe el uso del módulo `random` en Python. ¿Cuáles son las funciones más comunes de este módulo?
random se usa para valores pseudoaleatorios en simulaciones, datos de prueba
y tareas no criptográficas.
Funciones comunes:
random()devuelve un número en[0.0, 1.0);randint(a, b)devuelve un entero en un rango;randrange(start, stop, step)funciona comorange, pero devuelve un elemento aleatorio;choice(seq)/choices(seq, k=...);shuffle(list_)mezcla una lista in-place;sample(population, k)devuelve una muestra única.
Para criptografía, usa secrets, no random.
En resumen:
randomsirve para aleatoriedad de propósito general.- Las más usadas son
randint,choice,shuffleysample. - Para tareas sensibles a la seguridad, necesitas
secrets.
32. ¿Cómo trabajar con números con mejor precisión en Python?
Para cálculos financieros y decimales exactos, usa decimal.Decimal en lugar
de float.
from decimal import Decimal
total = Decimal("0.1") + Decimal("0.2") # Decimal('0.3')Para números racionales, también es útil fractions.Fraction.
En resumen:
floates cómodo, pero tiene errores por representación binaria.- Para aritmética decimal exacta, elige
Decimal. - El tipo numérico debe corresponder al dominio del problema.
33. ¿Qué son los caracteres de escape en strings de Python y cómo se usan? Da ejemplos de `\n`, `\t`, `\r`, `\"` y `\'`.
Las secuencias de escape son combinaciones especiales con \ que codifican
caracteres de control dentro de un string.
\nes nueva línea\tes tabulación\res retorno de carro\"es una comilla doble dentro de un string con"\'es una comilla simple dentro de un string con'
text = "A\tB\n\"quoted\"\nI\'m here\rX"En resumen:
- Los caracteres de escape controlan el formato del string.
- Permiten insertar comillas sin romper la sintaxis.
- Para rutas o regex, suele ser útil usar raw strings
r"...".
34. Explica el uso de los métodos `strip`, `lstrip` y `rstrip` en Python. ¿En qué se diferencian?
Estos métodos trabajan sobre los extremos del string:
strip()elimina caracteres de ambos lados;lstrip()solo del lado izquierdo;rstrip()solo del lado derecho.
Por defecto, eliminan whitespace o un conjunto específico de caracteres:
" hello ".strip() # "hello"
"--id--".strip("-") # "id"En resumen:
- La familia
stripno modifica el string in-place, sino que devuelve uno nuevo. - La diferencia está solo en el lado que se limpia.
- Se usa muy a menudo sobre datos de entrada.
35. Describe el propósito del método `count` en strings. ¿Cómo funciona?
str.count(sub[, start[, end]]) cuenta cuántas apariciones no superpuestas de
la subcadena sub hay en el rango indicado.
"banana".count("an") # 2
"banana".count("a", 2) # 2Devuelve 0 si no hay coincidencias.
En resumen:
countdevuelve rápidamente la cantidad de apariciones de una subcadena.- Soporta limitar la búsqueda con
start/end. - Cuenta solo apariciones no superpuestas.
36. ¿Cómo funciona el método `join` en Python y para qué se usa con más frecuencia?
sep.join(iterable) une una secuencia de strings en un solo string usando
sep como separador.
names = ["Ada", "Linus", "Guido"]
result = ", ".join(names) # "Ada, Linus, Guido"Usos típicos: construir strings tipo CSV, logs, fragmentos SQL, rutas URL y mensajes.
En resumen:
joines la forma estándar y rápida de concatenar strings.- Se llama sobre el separador, no sobre la lista.
- Es más eficiente que hacer
+=muchas veces en un bucle.
37. ¿Qué es slicing en Python y cómo se usa para obtener substrings? Da ejemplos con índices positivos y negativos.
Slicing es la selección de una parte de una secuencia mediante [start:stop:step].
s = "python"
s[1:4] # "yth"
s[:2] # "py"
s[-3:] # "hon"
s[::-1] # "nohtyp"start se incluye y stop no se incluye.
En resumen:
- Slicing funciona con strings, listas y tuplas.
- Los índices negativos se cuentan desde el final.
- Es una herramienta básica para procesar secuencias sin bucles.
38. Explica el método `replace` para strings. ¿Cómo puede usarse para reemplazar varias apariciones de una subcadena?
str.replace(old, new, count=-1) devuelve un nuevo string donde old se
reemplaza por new. Por defecto, reemplaza todas las apariciones.
"foo bar foo".replace("foo", "baz") # "baz bar baz"
"foo bar foo".replace("foo", "baz", 1) # "baz bar foo"En resumen:
replaceno modifica el string in-place.- Sin
count, reemplaza todas las apariciones. countpermite controlar cuántos reemplazos se hacen.
39. ¿Cómo funciona el método `split` en strings?
split(sep=None, maxsplit=-1) divide un string en una lista de subcadenas.
- sin
sep, divide por cualquier whitespace; - con
sep, divide por un separador concreto; maxsplitlimita el número de divisiones.
"a,b,c".split(",") # ["a", "b", "c"]
"a b c".split() # ["a", "b", "c"]
"k=v=x".split("=", 1) # ["k", "v=x"]En resumen:
splitconvierte un string en una lista de tokens.sep=Nonetiene un comportamiento especial para whitespace.maxsplites útil para parsear formatos key/value.
40. ¿Cómo funciona la conversión de tipos (type casting)?
Type casting es la conversión explícita de un valor entre tipos mediante
constructores como int(), float(), str(), bool(), list(), tuple(),
set() y dict().
age = int("42")
price = float("19.99")
text = str(10)Los datos incorrectos provocan una excepción como ValueError o TypeError,
por lo que la entrada externa requiere validación.
En resumen:
- Python ofrece funciones explícitas de conversión de tipos.
- No todos los valores pueden convertirse de forma segura.
- La entrada del usuario requiere validación y manejo de excepciones.
41. ¿Cómo convierte Python automáticamente distintos tipos de datos a valores booleanos?
En un contexto booleano, Python aplica las reglas de truthiness.
Valores falsy:
False,None;- números cero:
0,0.0,0j; - colecciones vacías:
"",[],{},set(),tuple(),range(0).
Todo lo demás suele ser True.
bool([]) # False
bool("0") # TrueEn resumen:
- La conversión a booleano se basa en reglas truthy/falsy.
- Lo vacío y lo cero dan
False. - El string no vacío
"0"sigue siendoTrue.
42. Explica cómo convertir un string a entero o a número decimal en Python. ¿Qué pasa si el string no puede convertirse?
Para convertir, usa int() y float():
count = int("42")
ratio = float("3.14")Si el string no es válido, Python lanza ValueError.
def parse_int(value: str) -> int | None:
try:
return int(value)
except ValueError:
return NoneEn resumen:
int()yfloat()realizan una conversión explícita desde string.- Un formato inválido produce
ValueError. - Para datos externos, hace falta
try/except.
43. ¿Qué hace Python al comparar distintos tipos de datos, como enteros y flotantes, o enteros y booleanos?
Python permite comparaciones numéricas entre numeric types compatibles.
intyfloatse comparan por valor.booles una subclase deint:False == 0,True == 1.
1 == 1.0 # True
True == 1 # True
False < 1 # TruePara tipos incompatibles como int y str, las comparaciones de orden como
< y > lanzan TypeError.
En resumen:
int,floatyboolcomparten una semántica numérica común.boolse comporta como0/1en comparaciones.- Los tipos incompatibles no se comparan con operadores de orden.
44. ¿Cómo maneja Python la comparación entre listas y sets?
list y set son tipos distintos con semánticas diferentes, por lo que la
comparación directa de orden entre ellos no está soportada.
[1, 2] == {1, 2} # False
[1, 2] < {1, 2} # TypeErrorSi necesitas comparar el contenido de elementos, primero normaliza el tipo:
set([1, 2]) == {1, 2} # TrueEn resumen:
listysetse comparan como estructuras de datos distintas.==entre ellas normalmente daFalse.- Para una comparación con sentido, primero conviértelas al mismo tipo.
45. Describe el uso de la función `bool()` en Python.
bool(x) devuelve el valor booleano de x según las reglas de truthiness.
Escenarios típicos:
- conversión explícita de un valor a
True/False; - condiciones más legibles;
- construcción de filtros.
bool("text") # True
bool("") # False
bool(0) # FalseEn resumen:
bool()unifica la comprobación de "vacío/no vacío".- Se basa en las reglas integradas de truthy/falsy.
- Es útil para validación y lógica condicional.
46. ¿Cuál es la diferencia entre `list` y `tuple`?
La diferencia principal es que list es mutable y tuple es immutable.
Consecuencias prácticas:
listsirve para datos que cambian;tuplesirve para registros fijos;tuplepuede usarse como clave de diccionario si sus elementos son hashables.
coords: tuple[float, float] = (50.45, 30.52)
queue: list[str] = ["task-1", "task-2"]En resumen:
listes para colecciones mutables.tuplees para estructuras de datos inmutables.- La elección afecta la seguridad de la API y su uso en
dict/set.
47. ¿Cómo se puede crear un `tuple`?
Formas principales:
t1 = (1, 2, 3)
t2 = 1, 2, 3
t3 = tuple([1, 2, 3])
single = (42,) # la coma es importante
empty = ()Para un solo elemento, la coma es obligatoria; de lo contrario, es solo una expresión entre paréntesis.
En resumen:
- Un
tuplese crea con un literal o contuple(iterable). single = (x,)es un tuple válido de un solo elemento.- El tuple vacío es
().
48. ¿Cuál es la diferencia entre el método `reverse` y el slicing `[::-1]` al invertir una lista?
list.reverse() modifica la lista existente in-place y devuelve None.
lst[::-1] crea una nueva lista invertida, es decir, una copia.
values = [1, 2, 3]
values.reverse() # values -> [3, 2, 1]
other = values[::-1] # nueva listaEn resumen:
reverse()modifica el original.[::-1]devuelve un objeto nuevo.- La elección depende de si necesitas conservar los datos originales.
49. Explica el propósito y el uso del método `sort` para listas. ¿Cómo ordenar una lista en orden descendente?
list.sort() ordena la lista in-place. Soporta estos parámetros:
key, una función de clave de ordenación;reverse=True, para orden descendente.
nums = [5, 1, 7]
nums.sort(reverse=True) # [7, 5, 1]Si necesitas una copia nueva ordenada, usa sorted(iterable).
En resumen:
sort()modifica la lista en el lugar.- Para orden descendente, usa
reverse=True. sorted()es conveniente cuando necesitas conservar el original.
50. ¿Cuál es la diferencia entre el método `copy` y la asignación directa de una lista a otra? ¿Por qué a veces es importante usar `copy`?
La asignación copia solo la referencia:
a = [1, 2]
b = a
b.append(3) # a también cambiaráa.copy() crea una lista nueva, superficial:
a = [1, 2]
b = a.copy()
b.append(3) # a no cambiaráPara estructuras anidadas, puede hacer falta copy.deepcopy.
En resumen:
=no copia los datos; comparte el mismo objeto entre variables.copy()aísla los cambios a nivel de la lista superior.- Para objetos anidados, usa
deepcopy.
51. ¿Qué hace el método `pop` en una lista? ¿En qué se diferencia su comportamiento si se indica un índice o no?
pop() elimina y devuelve un elemento de la lista.
pop()sin argumentos elimina el último elemento;pop(i)elimina el elemento con índicei.
items = ["a", "b", "c"]
last = items.pop() # "c"
first = items.pop(0) # "a"Un índice inválido produce IndexError.
En resumen:
popcombina eliminación y devolución del valor.- Sin índice, trabaja sobre el final de la lista.
- Con índice, elimina una posición concreta.
52. ¿Cómo unir dos listas en Python usando el operador `+` y el método `extend`? ¿Cuál es la diferencia clave entre ambos enfoques?
a + b crea una nueva lista, mientras que a.extend(b) modifica la lista
a in-place.
a = [1, 2]
b = [3, 4]
c = a + b # [1, 2, 3, 4], a no cambió
a.extend(b) # a -> [1, 2, 3, 4]En resumen:
+devuelve un objeto nuevo.extend()muta la lista existente.- La elección depende de si necesitas conservar el original.
53. ¿Para qué se usan las funciones `min`, `max`, `sum`, `all` y `any` en el contexto de listas?
Son agregadores básicos para colecciones iterables:
min()/max()para mínimo y máximo;sum()para suma de números;all()devuelveTruesi todos los elementos son truthy;any()devuelveTruesi al menos un elemento es truthy.
nums = [3, 10, 1]
min(nums), max(nums), sum(nums) # (1, 10, 14)
all([True, 1, "x"]) # True
any([0, "", None, 5]) # TrueEn resumen:
- Estas funciones reducen el código repetitivo en los bucles.
- Funcionan con cualquier iterable.
- Combinan bien con generadores para procesamiento perezoso.
54. ¿Qué es un diccionario (`dict`) en Python y en qué se diferencia de otras estructuras de datos?
dict es un array asociativo que almacena pares clave -> valor. Las claves
deben ser hashable y los valores pueden ser de cualquier tipo.
Diferencias:
- acceso por clave y no por índice;
- complejidad media de búsqueda, inserción y eliminación cercana a
O(1); - desde Python 3.7+ conserva el orden de inserción de las claves.
En resumen:
dictes óptimo para acceso rápido por clave.- Es la estructura principal para configuraciones y mappings.
- Las claves deben ser inmutables y hashable.
55. ¿Cuál es la diferencia entre obtener un valor de un diccionario con corchetes `[]` y con el método `get`?
d[key] devuelve el valor o lanza KeyError si la clave no existe.
d.get(key, default) devuelve el valor o default, o None, sin lanzar excepción.
config = {"timeout": 30}
config["timeout"] # 30
config.get("retries", 3) # 3En resumen:
[]sirve para claves obligatorias.get()sirve para campos opcionales.get()reduce la necesidad detry/exceptal leer datos.
56. Describe cómo se pueden actualizar y eliminar elementos de un diccionario.
Actualización:
d[key] = valueinserta o actualiza una sola clave;d.update({...})hace una actualización masiva.
Eliminación:
del d[key]elimina una clave, con error si no existe;d.pop(key, default)elimina y devuelve el valor;d.popitem()elimina el último par;d.clear()limpia todo el diccionario.
En resumen:
- Para upsert, sirven
[]yupdate(). - Para eliminación segura, suele usarse
pop(). - Elige la API según el comportamiento deseado si la clave falta.
57. ¿Para qué sirven los métodos `keys`, `values` e `items` en los diccionarios?
Estos métodos devuelven objetos vista del diccionario:
keys()devuelve todas las claves;values()devuelve todos los valores;items()devuelve pares(key, value).
data = {"a": 1, "b": 2}
for key, value in data.items():
...Las vistas son dinámicas: reflejan el estado actual del diccionario.
En resumen:
keys/values/itemssirven para iteración y comprobaciones.items()es lo más cómodo para recorrer clave y valor en un bucle.- No son copias, sino vistas "vivas" de los datos.
58. ¿Cómo está implementado `dict` internamente en Python?
dict está implementado como una hash table optimizada para memoria y acceso
rápido. La clave se hashea, se elige una celda por el hash y las colisiones se
resuelven con un algoritmo interno de probing.
Consecuencias prácticas:
- el acceso medio es cercano a
O(1); - la calidad de
__hash__y__eq__influye en el comportamiento; - los objetos mutables no pueden usarse como claves.
En resumen:
dictes una estructura hash de alto rendimiento.- Su velocidad se consigue gracias al hash.
- Las claves deben ser hashable y estables.
59. ¿Qué es una hash function?
Una hash function transforma un objeto en un número entero, el hash, que se
usa para ubicarlo o buscarlo rápidamente en dict y set.
Condiciones de corrección:
- si
a == b, entonceshash(a) == hash(b); - el valor del hash debe ser estable durante la vida del objeto.
En resumen:
- La hash function es la base del rendimiento rápido de
dictyset. - No garantiza unicidad, porque puede haber colisiones.
- En clases personalizadas,
__eq__y__hash__deben ser coherentes.
60. ¿Qué es `set` en Python y en qué se diferencia de otras estructuras de datos?
set es una colección no ordenada de elementos únicos.
Diferencias:
- elimina duplicados automáticamente;
- ofrece operaciones rápidas de pertenencia,
x in s; - soporta operaciones de teoría de conjuntos como unión, intersección y diferencia.
En resumen:
setes óptimo para unicidad y membership checks.- El orden de los elementos no está garantizado.
- Los elementos deben ser hashable.
61. ¿Cómo crear un `set` en Python y qué restricciones existen para sus elementos?
Creación:
s1 = {1, 2, 3}
s2 = set([1, 2, 2, 3]) # {1, 2, 3}
empty = set() # no {}Restricciones:
- los elementos deben ser hashable, por ejemplo
int,str,tuple; list,dictysetno pueden añadirse directamente.
En resumen:
set()crea un conjunto vacío.- Los duplicados se eliminan automáticamente.
- Solo se permiten elementos hashable.
62. ¿Para qué se usa el método `intersection()` en un set de Python y en qué se diferencia de `union()`?
intersection() devuelve los elementos comunes entre conjuntos, mientras que
union() reúne todos los elementos únicos de ambos conjuntos.
a = {1, 2, 3}
b = {3, 4}
a.intersection(b) # {3}
a.union(b) # {1, 2, 3, 4}En resumen:
intersectionequivale a intersección, lo común.unionequivale a unión, todo lo único.- Ambos métodos son básicos para comparar conjuntos de datos.
63. ¿Qué tipos de datos son immutable?
Tipos immutable comunes:
int,float,bool,complex;str,bytes;tuple, si sus elementos también son immutable;frozenset;NoneType.
En resumen:
- Un objeto immutable no puede cambiar después de crearse.
- En lugar de mutarlo, se crea un objeto nuevo.
- Estos tipos son más seguros como claves de
dicty elementos deset.
64. ¿Qué tipos de datos son mutable?
Tipos mutable comunes:
list;dict;set;bytearray;- la mayoría de objetos de clases definidas por el usuario.
En resumen:
- Los objetos mutable cambian in-place.
- Las mutaciones pueden generar efectos secundarios por referencias compartidas.
- Hay que tener cuidado con las copias.
65. ¿Por qué es importante entender la mutabilidad al trabajar con diccionarios o sets?
dict y set se basan en hashing, por lo que sus claves y elementos deben ser
estables y hashable. Los objetos mutable no pueden usarse con seguridad como
claves o elementos de un set.
Además, mutar valores a través de referencias compartidas suele producir bugs inesperados.
En resumen:
- La mutability afecta la corrección de claves en
dictyset. - Los objetos mutable compartidos suelen causar efectos secundarios.
- Las copias explícitas y el control de mutaciones reducen bugs.
66. ¿Qué es `None`?
None es un valor singleton especial del tipo NoneType que significa
"ausencia de valor".
Escenarios típicos:
- una función no devuelve nada explícitamente;
- parámetros opcionales;
- marcador de "dato aún no definido".
La comprobación se hace con is:
if value is None:
...En resumen:
Nonesignifica ausencia de valor.- Es un singleton, por eso se compara con
is. - Se usa a menudo en APIs como estado opcional.
67. ¿Qué es `id` en Python, cómo se usa y por qué es importante?
id(obj) devuelve el identificador del objeto, único dentro del ciclo de vida
del objeto en el proceso actual.
Es útil para diagnóstico:
- saber si es el mismo objeto;
- verificar si se creó una copia;
- detectar si hubo mutación de una referencia compartida.
En resumen:
idayuda a analizar la identidad de los objetos.- Es útil al depurar copias y mutaciones.
- No se usa como identificador de negocio.
68. ¿Cuál es la diferencia entre los operadores `is` y `==`?
== compara valores, es decir equivalencia, mientras que is compara
identidad, o sea si se trata del mismo objeto en memoria.
a = [1, 2]
b = [1, 2]
a == b # True
a is b # FalseImportante sobre optimizaciones, interning: CPython cachea enteros pequeños
de -5 a 256 y strings cortos durante compilación o carga. Por eso, is
puede devolver True incluso si parecen creados por separado. Pero esto es un
detalle de implementación y no debe usarse en lógica de negocio.
Para None, usa siempre is.
En resumen:
==trata sobre igualdad de valores.istrata sobre el mismo objeto en memoria.- El interning puede producir un
is Trueinesperado paraintystrpequeños. value is Nonees la única forma correcta de comprobarNone.
69. ¿Cómo se aplica el patrón Singleton en Python? Da ejemplos de objetos de instancia única en Python.
Singleton significa una sola instancia global de un objeto dentro del proceso. En Python, a menudo se sustituye por el nivel de módulo, ya que los módulos se importan una sola vez.
Ejemplos de objetos de instancia única del lenguaje:
None;TrueyFalse;Ellipsis.
En código de aplicación, en lugar de un Singleton rígido, suele preferirse un contenedor de DI o factorías para mejorar la testabilidad.
En resumen:
- Python ya tiene objetos de instancia única integrados.
- A menudo basta con el alcance de módulo sin aplicar un patrón separado.
- Abusar de Singleton empeora la testabilidad.
70. ¿Para qué se usan los operadores `break` y `continue` en los bucles de Python?
break finaliza el bucle de forma anticipada, mientras que continue omite la
iteración actual y pasa a la siguiente.
for n in range(10):
if n == 5:
break
if n % 2 == 0:
continueEn resumen:
breakdetiene el bucle por completo.continueomite solo el paso actual.- Hacen que el control del bucle sea explícito y legible.
71. Explica el concepto de un bucle infinito. ¿Cuándo es apropiado usarlo y cómo asegurar su terminación correcta?
Un bucle infinito es un bucle sin una condición natural de finalización, por
ejemplo while True. Se usa para procesos en segundo plano o de trabajo, sondeo y lógica de
bucle de eventos.
Terminación correcta:
- una condición
breakclara; - manejo de señales de terminación;
- tiempos de espera y
try/finallypara la limpieza final.
while True:
task = queue.get()
if task is None:
break
handle(task)En resumen:
while Truees adecuado para bucles de servicio de larga duración.- Hace falta un mecanismo de parada controlado.
- Siempre hay que prever la liberación de recursos.
72. Describe cómo funcionan los bucles anidados en Python. ¿Qué problemas de rendimiento pueden causar y cómo evitarlos?
Un bucle anidado es un bucle dentro de otro. La complejidad suele convertirse
en O(n*m) o peor, lo que resulta crítico con grandes volúmenes de datos.
Optimizaciones:
- sustituir búsquedas en listas por
setodict; - sacar los invariantes fuera del bucle interno;
- usar generadores,
itertoolsy vectorización; - perfilar las zonas calientes.
En resumen:
- Los bucles anidados multiplican rápidamente el coste computacional.
- Las estructuras de datos suelen ser más importantes que las microoptimizaciones.
- El perfilado muestra exactamente qué debe optimizarse.
73. ¿Qué es la coincidencia estructural de patrones (`match`/`case`)?
match/case en Python 3.10+ es un mecanismo para analizar estructuras de datos
mediante patrones. Funciona con literales, tipos, secuencias, diccionarios y
clases.
def handle(message: dict[str, object]) -> str:
match message:
case {"type": "ping"}:
return "pong"
case {"type": "user", "id": int(user_id)}:
return f"user:{user_id}"
case _:
return "unknown"En resumen:
match/casese lee mejor en ramificaciones complejas.- Permite comprobar la forma y extraer datos al mismo tiempo.
- Es especialmente útil para protocolos, eventos y parsing de estructuras.
74. ¿En qué casos la coincidencia de patrones es mejor que `if`/`elif`?
match/case es mejor cuando necesitas:
- comprobar muchas formas de datos mutuamente excluyentes;
- desempaquetar estructuras anidadas;
- evitar cadenas largas de
if/elif.
if/elif es mejor para condiciones booleanas simples y lógica breve.
En resumen:
- Para escenarios estructurales, es mejor
match/case. - Para condiciones simples, basta con
if/elif. - El criterio de elección es la legibilidad y el mantenimiento.
75. ¿Qué es una función en Python?
Una función es un bloque de código invocable con nombre que recibe argumentos, devuelve un resultado y permite encapsular lógica.
def normalize_name(name: str) -> str:
return name.strip().title()Las funciones soportan valores por defecto, argumentos nombrados, *args/**kwargs,
anotaciones de tipos y decoradores.
En resumen:
- La función es la unidad básica de reutilización de código.
- Define un contrato claro mediante parámetros y valor de retorno.
- Las anotaciones de tipos hacen explícito ese contrato.
76. ¿Qué tipos de argumentos de función existen?
En Python moderno:
- positional-only (
/); - posicional o nombrado;
- solo nombrado (
*); - variadic positional (
*args); - variadic keyword (
**kwargs).
def f(a, /, b, *, c, **kwargs):
...En resumen:
- Python ofrece un control flexible sobre cómo se llama una función.
/y*formalizan el contrato de la API.*args/**kwargsson útiles para interfaces extensibles.
77. ¿Qué son los argumentos posicionales y los argumentos nombrados?
Los argumentos posicionales se pasan por orden, y los argumentos nombrados se pasan por nombre del parámetro.
def connect(host: str, port: int) -> str:
return f"{host}:{port}"
connect("localhost", 5432) # posicional
connect(host="localhost", port=5432) # nombradoEn resumen:
- Los posicionales dependen del orden de los parámetros.
- Los nombrados mejoran la legibilidad de la llamada.
- Se pueden combinar respetando las reglas de la firma.
78. ¿Qué son los argumentos por defecto y qué problemas pueden tener?
Los argumentos por defecto se usan cuando no se pasa un valor en la llamada. Se evalúan una sola vez al definir la función.
Problema: valor mutable por defecto.
def add_item(item: int, bucket: list[int] | None = None) -> list[int]:
if bucket is None:
bucket = []
bucket.append(item)
return bucketEn resumen:
- Los valores por defecto son cómodos para valores estables.
- Un valor mutable por defecto puede acumular estado entre llamadas.
- El patrón seguro es
Nonemás inicialización interna.
79. Explica el propósito y uso de `*args` y `**kwargs` en funciones de Python. ¿En qué se diferencian?
*args recoge argumentos posicionales adicionales en un tuple. **kwargs
recoge argumentos nombrados adicionales en un dict.
def log_event(event: str, *args: object, **kwargs: object) -> None:
...Usos típicos: envoltorios, adaptadores de API, decoradores y reenvío de parámetros.
En resumen:
*argssignifica posicionales adicionales.**kwargssignifica nombrados adicionales.- Hacen las funciones más flexibles, pero requieren validación clara.
80. ¿Cómo definir una función con anotaciones de tipos en Python? Da un ejemplo y explica las ventajas de este enfoque.
Los tipos se indican en la firma de los parámetros y en el valor de retorno.
def process_users(users: list[dict[str, object]]) -> list[str]:
return [str(user["name"]) for user in users if bool(user.get("active"))]Ventajas:
- mejor experiencia de desarrollo, como autocompletado y navegación;
- comprobación estática en CI;
- contrato explícito para otros desarrolladores.
En resumen:
- Las anotaciones de tipos documentan la API.
- Reducen el riesgo de errores de integración.
- Aportan más valor en bases de código medianas y grandes.
81. ¿Qué son las funciones lambda?
lambda es una función anónima de una sola expresión. Normalmente se usa para
funciones de devolución de llamada cortas en sorted, map y filter.
users = [{"name": "Ada"}, {"name": "Bob"}]
users_sorted = sorted(users, key=lambda u: u["name"])Para lógica compleja, es mejor una función normal definida con def.
En resumen:
lambdaes cómoda para expresiones locales cortas.- Está limitada a una sola expresión.
- Para mantener la legibilidad, el código complejo debe ir en una
def.
82. ¿Cuál es el alcance de las variables dentro de una función?
Python usa la regla LEGB para buscar nombres:
Local;Enclosing, funciones externas;Global, módulo;Builtins, nombres integrados.
Dentro de una función, una asignación crea una variable local si no se declara
global o nonlocal.
En resumen:
- El alcance define dónde un nombre está disponible y puede modificarse.
- LEGB explica el orden de búsqueda de variables.
- Entender mal el alcance suele provocar
UnboundLocalError.
83. ¿Qué son las variables locales y globales en Python?
Las variables locales viven dentro del cuerpo de una función. Las variables globales se definen a nivel de módulo.
Para modificar una global desde una función hace falta global, pero
normalmente conviene evitarlo por las dependencias implícitas que crea.
En resumen:
- Las locales son más seguras para mantenimiento y pruebas.
- Las globales simplifican el acceso, pero complican el control del estado.
- Es mejor pasar las dependencias como parámetros.
84. ¿Cuál es la diferencia entre variables locales y nonlocal en Python?
nonlocal se usa dentro de una función anidada para modificar una variable del
alcance envolvente más cercano, no del alcance global.
from collections.abc import Callable
def counter() -> Callable:
value = 0
def inc() -> int:
nonlocal value
value += 1
return value
return incEn resumen:
- Una variable local pertenece a la función actual.
nonlocalmodifica el estado de la función externa.- Es un mecanismo clave para cierres con estado.
85. ¿Qué es el desempaquetado en Python y cómo se aplica en asignaciones?
El desempaquetado es descomponer los elementos de una secuencia o estructura en variables separadas.
x, y = (10, 20)
first, *middle, last = [1, 2, 3, 4]También funciona con diccionarios en match/case, en llamadas de funciones y
en bucles.
En resumen:
- El desempaquetado hace el código más compacto y legible.
- Soporta la captura del resto de valores con el operador estrella.
- Se usa a menudo al parsear estructuras de datos.
86. Explica el concepto de empaquetado de valores en Python y da ejemplos.
El empaquetado es reunir varios valores en una sola estructura como tuple, list o
dict.
point = 10, 20 # empaquetado en tuple
def collect(*args: int) -> tuple[int, ...]:
return args*args y **kwargs son ejemplos típicos de empaquetado de argumentos.
En resumen:
- El empaquetado reúne muchos valores en un solo contenedor.
- Se usa con mucha frecuencia en firmas de funciones.
- Combina bien con el desempaquetado del lado de la llamada.
87. ¿Para qué se usa el operador `*` en el empaquetado y desempaquetado?
En parámetros de función, * empaqueta argumentos posicionales en *args, y
en la llamada desempaqueta un iterable en argumentos posicionales.
def add(a: int, b: int) -> int:
return a + b
nums = [2, 3]
add(*nums) # 5También se usa en asignaciones para capturar "el resto" de los elementos.
En resumen:
*es un operador universal para trabajar con argumentos variables.- En la firma empaqueta y en la llamada desempaqueta.
- Reduce el código repetitivo al pasar datos.
88. ¿Qué es un cierre y qué relación tiene con los decoradores?
Un cierre es una función interna que "recuerda" las variables del alcance envolvente incluso después de que la función externa haya terminado.
Un decorador suele implementarse precisamente mediante un cierre: la envoltura conserva una referencia a la función original y a parámetros adicionales.
En resumen:
- Un cierre es función más contexto capturado.
- Un decorador suele ser una aplicación práctica de un cierre.
- Permite añadir comportamiento sin cambiar el cuerpo de la función.
89. ¿Qué es un decorador en Python y cómo funciona?
Un decorador es un objeto invocable que recibe una función o clase y devuelve una versión modificada, una envoltura.
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapperAplicaciones típicas: registro, caché, autorización, reintentos y métricas.
En resumen:
- Un decorador añade comportamiento transversal.
- Funciona envolviendo un objeto invocable.
- Evita duplicar lógica técnica dentro del código de negocio.
90. ¿Se pueden usar varios decoradores en una misma función?
Sí, se pueden apilar varios decoradores. Se aplican de abajo hacia arriba: el
más cercano a def envuelve primero.
@decorator_a
@decorator_b
def handler() -> None:
...Equivalente: handler = decorator_a(decorator_b(handler)).
En resumen:
- Usar varios decoradores es válido y común.
- El orden de aplicación importa.
- La pila de decoradores debe documentarse para mantener claridad.
91. Describe un posible problema con el orden de los decoradores al aplicarlos a una función.
Un orden incorrecto puede cambiar la semántica: por ejemplo, aplicar caché antes de una comprobación de acceso puede almacenar un resultado no deseado o saltarse la lógica esperada.
Riesgos típicos:
- el registro ve argumentos ya modificados;
- los reintentos envuelven la excepción equivocada;
- la caché se aplica antes o después de la validación en el punto incorrecto.
En resumen:
- El orden de los decoradores afecta al comportamiento de la función.
- Es una causa frecuente de bugs ocultos.
- Las cadenas críticas necesitan pruebas sobre el orden de ejecución.
92. ¿Se puede crear un decorador con una clase?
Sí. Una clase decoradora implementa __call__ para que su instancia se comporte
como una función.
class CallCounter:
def __init__(self, func):
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs):
self.calls += 1
return self.func(*args, **kwargs)En resumen:
- Un decorador puede implementarse no solo con una función, sino también con una clase.
- La clase es útil cuando se necesita estado interno.
- El mecanismo clave es
__call__.
93. ¿Cómo definir un decorador que acepta parámetros?
Hace falta una estructura de tres niveles: fábrica de decoradores, decorador y envoltura.
from functools import wraps
def retry(times: int):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times - 1):
try:
return func(*args, **kwargs)
except Exception:
pass
return func(*args, **kwargs)
return wrapper
return decoratorEn resumen:
- Un decorador parametrizado es una función que devuelve otro decorador.
- A menudo se usa un cierre para conservar los parámetros.
- Este enfoque es cómodo para comportamiento configurable.
94. ¿Para qué se usa `functools.wraps` en funciones decoradoras?
functools.wraps copia los metadatos de la función original a la envoltura: nombre,
docstring, módulo y también __wrapped__.
Esto es importante para:
- depuración y logs correctos;
- introspección y documentación;
- compatibilidad con herramientas que leen la firma.
En resumen:
wrapsconserva la "identidad" de la función original.- Sin él, las funciones decoradas pierden metadatos útiles.
- Es una buena práctica para todos los decoradores de envoltura.
95. ¿Cómo funcionan dict comprehension, list comprehension y set comprehension?
Una comprehension crea una colección a partir de una expresión y uno o varios bucles, opcionalmente con filtro.
squares = [x * x for x in range(6)]
mapping = {x: x * x for x in range(6)}
unique = {x % 3 for x in range(10)}Formato:
- list:
[expr for x in it if cond] - set:
{expr for x in it if cond} - dict:
{k_expr: v_expr for x in it if cond}
En resumen:
- Una comprehension expresa transformaciones de datos de forma compacta.
- Funciona con
list,setydict. - Produce un código más limpio que agregar elementos manualmente en un bucle.
96. ¿Qué ventajas tienen las list comprehensions frente a los bucles normales?
Ventajas:
- código más corto y declarativo;
- menos variables auxiliares;
- normalmente un poco mejor de rendimiento en CPython;
- menor riesgo de olvidar
append.
Desventaja: para lógica muy compleja, una comprehension empeora la legibilidad.
En resumen:
- Una list comprehension es ideal para transformaciones simples.
- A menudo es más rápida y más limpia que un bucle manual.
- Para ramas complejas, es mejor un
fornormal.
97. ¿Puedes dar un ejemplo de una list comprehension anidada?
Ejemplo de flatten de una matriz:
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [item for row in matrix for item in row] # [1, 2, 3, 4, 5, 6]Ejemplo con condición:
pairs = [(x, y) for x in range(3) for y in range(3) if x != y]En resumen:
- Una comprehension anidada son varios
fordentro de una sola expresión. - El orden de los
forcorresponde al de los bucles anidados. - Úsala cuando la expresión siga siendo legible.
98. ¿Qué es más rápido en Python: una list comprehension o crear una lista con un bucle?
En escenarios típicos, una list comprehension es un poco más rápida que un
bucle con append, porque tiene bytecode optimizado y menos sobrecarga.
Importante: la mejora real depende del cuerpo de la operación, así que en zonas
críticas conviene medir con timeit o pyperf.
En resumen:
- A menudo la list comprehension es más rápida.
- La diferencia puede ser pequeña.
- Para decisiones de producción, guíate por mediciones.
99. ¿Qué es un iterador en Python?
Un iterador es un objeto que devuelve elementos secuencialmente y recuerda su estado actual. Implementa el protocolo:
__iter__()devuelve a sí mismo;__next__()devuelve el siguiente elemento o lanzaStopIteration.
En resumen:
- Un iterador da acceso elemento por elemento sin cargar todos los datos.
- Es la base de
for, de los generadores y del procesamiento perezoso. - Una vez agotado, un iterador no se reinicia solo.
100. ¿Cómo crear un iterador a partir de un objeto iterable usando la función `iter()`?
Llama a iter(iterable) para obtener un iterador.
items = [10, 20, 30]
it = iter(items)Después de eso, los valores se leen con next(it).
En resumen:
iter()convierte un iterable en un iterador.- Es una forma explícita de controlar la iteración manualmente.
- Se usa en procesamiento de flujos de datos a bajo nivel.
101. ¿Para qué sirve la función `next()` al trabajar con iterators?
next(iterator[, default]) devuelve el siguiente elemento del iterador. Cuando
los elementos se han agotado, lanza StopIteration o devuelve default si se
ha pasado.
it = iter([1, 2])
next(it) # 1
next(it) # 2
next(it, None) # NoneEn resumen:
next()da control manual sobre cada paso de la iteración.- Sin
default, el final del flujo produceStopIteration. - Con
default, se puede leer el flujo de forma segura.
102. ¿Se pueden usar de forma intercambiable los métodos `__next__()` y `__iter__()` con las funciones `next()` e `iter()`?
Sí, pero con el matiz del protocolo:
iter(obj)llama aobj.__iter__();next(it)llama ait.__next__().
Es decir, estas funciones son la interfaz estándar para los métodos dunder y normalmente se usan en lugar de invocarlos directamente.
En resumen:
iter()corresponde a__iter__().next()corresponde a__next__().- En código de aplicación, es mejor usar las funciones integradas.
103. ¿Qué papel tiene `StopIteration` en el diseño de iterators y cuándo suele aparecer?
StopIteration indica que el iterador se ha agotado. El bucle for lo captura
automáticamente y finaliza la iteración.
Normalmente aparece:
- al llamar a
next(it)después del último elemento; - en iterators personalizados, cuando los datos se terminan.
En resumen:
StopIterationes el final normal del flujo.- No debe registrarse como "error" dentro del flujo normal.
- En iterators propios, debe lanzarse correctamente.
104. ¿Qué es un generador y en qué se diferencia de un iterador o de una función normal?
Un generador es un iterador especial que se crea con una función que usa
yield. Genera valores uno por uno y conserva su estado interno entre llamadas.
Diferencias:
- frente a una función normal: no termina con un único
return, sino que funciona con "pausa/reanudación"; - frente a un iterador manual: tiene una implementación más simple, sin una
clase explícita con
__next__.
En resumen:
- Un generador es la forma más cómoda de hacer iteración perezosa.
- Requiere menos código que una clase iteradora personalizada.
- Es especialmente útil para grandes flujos de datos.
105. ¿Cómo se crea una función generadora?
Hay que definir una función con yield.
def countdown(start: int):
current = start
while current > 0:
yield current
current -= 1La llamada countdown(3) devuelve un objeto generador que se puede iterar.
En resumen:
- La presencia de
yieldconvierte la función en un generador. - Un generador devuelve valores por etapas.
- El estado de la función se conserva entre iteraciones.
106. ¿Cómo proporciona la palabra clave `yield` la funcionalidad de los generadores y por qué ahorran memoria?
yield devuelve el siguiente valor y "congela" el contexto de la función
(variables locales, posición de ejecución). El siguiente next() reanuda la
ejecución desde ese punto.
El ahorro de memoria se debe a que los datos no se crean por completo de antemano, sino que se calculan bajo demanda.
En resumen:
yieldimplementa la pausa y la reanudación de la ejecución.- Un generador soporta evaluación perezosa.
- Esto reduce el consumo de memoria en conjuntos de datos grandes.
107. ¿Cuál es la diferencia entre `return` y `yield`?
return finaliza la función y devuelve un único valor final. yield devuelve
un valor intermedio y conserva el estado para poder continuar después.
En un generador, return significa el final de la iteración (StopIteration).
En resumen:
return-> finalización de la función.yield-> entrega de valores por etapas.yieldse usa para procesamiento en flujo.
108. ¿Qué es la programación orientada a objetos (POO) y cuáles son sus principios principales en Python?
La POO es un enfoque en el que los datos y el comportamiento se agrupan en objetos.
Principios clave:
- encapsulación;
- herencia;
- polimorfismo;
- abstracción.
En Python, la POO suele combinarse con composición y tipado por comportamiento.
En resumen:
- La POO estructura el dominio mediante clases y objetos.
- Python soporta la POO de forma flexible, sin demasiado código ceremonial.
- En la práctica, la composición suele ser mejor que una herencia profunda.
109. ¿Qué es una `class` en Python?
Una class es una plantilla para crear objetos con atributos y
métodos.
class User:
def __init__(self, name: str) -> None:
self.name = nameLa clase define la estructura y el comportamiento de futuras instancias.
En resumen:
- Una clase describe los datos y las operaciones sobre ellos.
- A partir de una clase se crean objetos (instancias).
- Es la unidad básica de modelado en POO.
110. ¿Cómo se crea un objeto en Python?
Un objeto se crea llamando a la clase:
class User:
def __init__(self, name: str) -> None:
self.name = name
user = User("Ada")Durante la creación, se ejecuta __init__ para inicializar el estado.
En resumen:
- Objeto = instancia de una clase.
- Creación:
instancia = ClassName(...). __init__configura los atributos iniciales.
111. ¿Qué son los objetos y los atributos?
Un objeto es una instancia concreta de un tipo o clase. Un atributo es un par nombre-valor asociado al objeto (dato o método).
user.name # atributo de datos
user.save() # atributo métodoEn resumen:
- Un objeto almacena estado y comportamiento.
- Los atributos describen ese estado y comportamiento.
- El acceso a los atributos se hace con notación de punto.
112. ¿Qué papel cumple el método `__init__()` en una clase?
__init__ es el inicializador de la instancia: se llama después de crear el
objeto y rellena su estado inicial.
class Account:
def __init__(self, owner: str, balance: float = 0.0) -> None:
self.owner = owner
self.balance = balanceEn resumen:
__init__define los atributos iniciales del objeto.- Funciona como punto de entrada para configurar la instancia.
- No crea el objeto, solo lo inicializa.
113. ¿Para qué sirve el parámetro `self` en los métodos de clase de Python?
self es una referencia a la instancia actual. A través de él, el método lee y
modifica los atributos de la instancia.
def deposit(self, amount: float) -> None:
self.balance += amountEl nombre self no está reservado por la sintaxis, pero es el estándar
aceptado de forma general.
En resumen:
selfvincula el método con un objeto concreto.- A través de
selfse accede a los datos de la instancia. - Es el primer parámetro obligatorio de un método de instancia.
114. ¿Cómo se definen los métodos dentro de una clase?
Los métodos se definen como funciones dentro de una class.
class Calculator:
def add(self, a: int, b: int) -> int:
return a + bTipos habituales: de instancia (self), de clase (@classmethod, cls) y estático
(@staticmethod, sin self/cls).
En resumen:
- Un método es una función dentro del cuerpo de una clase.
- El tipo de método lo determinan el decorador y la firma.
- El más común es el método de instancia.
115. Explique el concepto de "todo es un objeto" en Python y dé ejemplos.
En Python, casi todo es un objeto: números, strings, funciones, clases y módulos. Por eso, todo tiene tipo, atributos y comportamiento.
type(10) # <class 'int'>
type(len) # <class 'builtin_function_or_method'>
type(str) # <class 'type'>En resumen:
- Un modelo de objetos unificado simplifica el lenguaje.
- Las funciones y las clases también son objetos de primera clase.
- Esto hace que Python sea flexible para metaprogramación.
116. Dé un ejemplo de una clase con métodos que realicen cálculos basados en atributos.
class Rectangle:
def __init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)En resumen:
- Los métodos pueden calcular valores a partir de los atributos de la instancia.
- Esto encapsula la lógica de dominio dentro del objeto.
- La API de la clase se vuelve más autodocumentada.
117. Describa una situación en la que pueda ser necesario usar varias clases en un programa de Python.
Cuando el dominio tiene varias responsabilidades, se reparten entre distintas
clases. Por ejemplo, en e-commerce: Order, OrderItem, PaymentService,
InventoryService.
Esto aporta:
- una separación clara de responsabilidades;
- menor acoplamiento;
- sustitución y pruebas de componentes más simples.
En resumen:
- Varias clases hacen falta para modelar un dominio complejo.
- Separar responsabilidades mejora el mantenimiento.
- La composición de clases suele ser mejor que un "god object".
118. ¿Cuál es la diferencia entre método de instancia, método de clase y método estático?
- Método de instancia: tiene
selfy trabaja con una instancia concreta. - Método de clase: tiene
clsy trabaja con la clase en general. - Método estático: no tiene
self/cls; es una función utilitaria dentro del espacio de nombres de la clase.
En resumen:
- Instancia -> lógica de la instancia.
- Clase -> lógica de la clase o constructores alternativos.
- Static -> lógica auxiliar sin acceso al estado.
119. ¿Qué es `@classmethod` en Python y en qué se diferencia de los métodos normales?
@classmethod pasa la clase (cls) como primer argumento, no la instancia. A
menudo se usa para métodos factoría.
class User:
def __init__(self, name: str) -> None:
self.name = name
@classmethod
def from_email(cls, email: str) -> User:
return cls(email.split("@")[0])En resumen:
classmethodtrabaja a nivel de clase.- Es cómodo para constructores alternativos.
- No requiere una instancia ya creada.
120. ¿Qué es `@staticmethod` en las clases de Python y cuándo conviene usarlo?
@staticmethod define un método sin self ni cls automáticos. Pertenece
lógicamente a la clase, pero no depende de su estado.
class Math:
@staticmethod
def clamp(value: int, min_v: int, max_v: int) -> int:
return max(min_v, min(value, max_v))En resumen:
staticmethodes una función dentro del espacio de nombres de la clase.- Úsalo para lógica auxiliar sin acceso a atributos.
- No sirve si necesitas estado de instancia o de clase.
121. ¿Cuál es la diferencia entre `@classmethod` y `@staticmethod` en las clases de Python?
La diferencia está en el primer argumento y en el nivel de acceso:
@classmethodrecibeclsy puede trabajar con el estado de la clase;@staticmethodno recibe nada automáticamente.
classmethod se usa más a menudo para factorías o constructores polimórficos,
y staticmethod para utilidades.
En resumen:
classmethodconoce la clase.staticmethodestá aislado del estado de clase y de instancia.- La elección depende de si necesitas acceso a
cls.
122. ¿Qué son los atributos de instancia en las clases de Python y en qué se diferencian de los atributos de clase?
Los atributos de instancia pertenecen a un objeto concreto (self.x). Los
atributos de clase pertenecen a la clase y se comparten entre todas las
instancias.
class User:
role = "member" # atributo de clase
def __init__(self, name: str) -> None:
self.name = name # atributo de instanciaEn resumen:
- Los atributos de instancia almacenan estado individual.
- Los atributos de clase almacenan configuración compartida.
- Las mutaciones de atributos de clase afectan a todas las instancias.
123. ¿Qué es `__slots__` en Python?
__slots__ limita el conjunto de atributos permitidos en una clase y puede
reducir el consumo de memoria al eliminar __dict__ en las instancias.
class Point:
__slots__ = ("x", "y")
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = yMatices de uso:
- Ahorro de memoria: los objetos ocupan bastante menos espacio, porque los
atributos se almacenan en un array fijo y no en la tabla hash
__dict__. - Velocidad: el acceso a atributos con
__slots__suele ser un poco más rápido. - Ausencia de
__dict__: no podrás añadir dinámicamente nuevos atributos que no estén en la lista__slots__(a menos que añadas"__dict__"a la propia lista). - Referencias débiles: si quieres usar
weakref, debes añadir explícitamente"__weakref__"a__slots__.
En resumen:
__slots__es útil para millones de objetos ligeros, por ejemplo nodos de un grafo.- Elimina
__dict__y__weakref__por defecto. - Reduce flexibilidad a cambio de rendimiento y control.
124. ¿Qué son los magic methods (métodos dunder) en las clases de Python y por qué se llaman "mágicos"?
Los métodos dunder (__init__, __str__, __len__, __eq__, ...) son puntos
de enganche especiales que Python llama automáticamente en respuesta a
operadores y funciones integradas.
Se llaman "mágicos" porque integran tu clase en el comportamiento del lenguaje.
En resumen:
- Los métodos dunder definen el comportamiento protocolario del objeto.
- Permiten que tus clases se comporten "como tipos integrados".
- Úsalos solo cuando haya una necesidad semántica clara.
125. ¿Para qué sirve el método `__del__`?
__del__ es un finalizador que puede llamarse antes de que el GC destruya el
objeto. Su comportamiento no es determinista, así que para gestionar recursos
es mejor usar gestores de contexto (with) y cierre explícito.
En resumen:
__del__no garantiza una ejecución oportuna.- No apoyes una limpieza crítica solo en él.
- El enfoque recomendado es
withotry-finally.
126. Dé un ejemplo de uso del método mágico `__str__` para definir la representación textual de una clase propia en Python.
class User:
def __init__(self, name: str, active: bool) -> None:
self.name = name
self.active = active
def __str__(self) -> str:
status = "active" if self.active else "inactive"
return f"User(name={self.name}, status={status})"str(user) y print(user) usarán __str__.
En resumen:
__str__da una representación comprensible para humanos del objeto.- Es útil para logs y salida en CLI.
- Debe ser corto y legible.
127. ¿Para qué se usan `__str__` y `__repr__`?
__str__ está orientado al lector final. __repr__ está orientado al
desarrollador y a la depuración, y es deseable que sea inequívoco.
print(obj) usa principalmente __str__, mientras que REPL y repr(obj) usan
__repr__.
En resumen:
__str__sirve para una salida amigable.__repr__sirve para una representación técnica.- Es buena práctica tener ambos si la clase es de dominio.
128. ¿Cómo funciona la sobrecarga de operadores en Python y por qué es útil?
La sobrecarga de operadores es la implementación de métodos dunder de
operadores (__add__, __sub__, __eq__, ...) para que los objetos de tu
propia clase soporten operadores.
class Vector2:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __add__(self, other: "Vector2") -> "Vector2":
return Vector2(self.x + other.x, self.y + other.y)En resumen:
- Da una sintaxis natural para tipos de dominio.
- Mejora la expresividad de la API.
- Es importante mantener una semántica matemáticamente esperable.
129. ¿Qué es la herencia?
La herencia permite crear una clase hija que hereda atributos y métodos de una clase base y puede ampliar o sobrescribir su comportamiento.
En resumen:
- La herencia favorece la reutilización de código.
- La clase hija puede sobrescribir métodos de la clase base.
- Una jerarquía excesiva complica el mantenimiento, por eso la composición suele ser mejor.
130. ¿Qué es la herencia simple en Python?
La herencia simple significa que una clase tiene solo un padre directo.
class Animal:
...
class Dog(Animal):
...Esta es la forma más simple y normalmente la más legible de herencia.
En resumen:
- Una clase hija -> una clase base.
- Modelo de resolución de métodos simple.
- Suele ser suficiente para la mayoría de modelos de negocio.
131. ¿Cómo se implementa la herencia en las clases de Python y qué sintaxis se usa para ello?
La sintaxis es class Child(Base):.
class Base:
def greet(self) -> str:
return "hello"
class Child(Base):
def greet(self) -> str:
return "hi"Si necesitas llamar a la lógica base, usa super().
En resumen:
- La herencia se define entre paréntesis después del nombre de la clase.
- La clase hija recibe la API de la clase base.
- La sobrescritura permite adaptar el comportamiento.
132. ¿Cómo acceder a los miembros de la clase base desde una clase hija?
El acceso es posible directamente a través de atributos y métodos heredados o
mediante super().
class Base:
def greet(self) -> str:
return "hello"
class Child(Base):
def greet(self) -> str:
return super().greet() + " world"En resumen:
- Los miembros heredados están disponibles automáticamente en la clase hija.
super()llama correctamente a la lógica de la clase base.- Esto es importante para ampliar comportamiento, no para duplicarlo.
133. ¿Para qué sirve la función `super()` en la herencia de Python y cómo se usa?
super() devuelve un proxy a la siguiente clase según el MRO para llamar a sus
métodos. Se usa típicamente en __init__ y en herencia múltiple cooperativa.
class Child(Base):
def __init__(self, value: int) -> None:
super().__init__()
self.value = valueEn resumen:
super()llama a la implementación padre sin fijar el nombre de la clase.- Ayuda a mantener la corrección del MRO.
- Es la forma recomendada de trabajar con herencia.
134. Describa la función `isinstance()` en Python y dé un ejemplo de uso.
isinstance(obj, cls_or_tuple) comprueba si un objeto pertenece a un tipo o a
su subclase.
isinstance(10, int) # True
isinstance(True, int) # True
isinstance("x", (str, bytes)) # TrueEn resumen:
isinstancees más seguro quetype(obj) is ...en código polimórfico.- Tiene en cuenta la jerarquía de herencia.
- Soporta tuplas de tipos.
135. Explique la función `issubclass()` en Python y dé un ejemplo de uso.
issubclass(Sub, Base) comprueba si la clase Sub es subclase de Base.
class Animal: ...
class Dog(Animal): ...
issubclass(Dog, Animal) # TrueEn resumen:
- Trabaja con clases, no con instancias.
- Es útil para validar APIs a nivel de tipos.
- Tiene en cuenta la herencia transitiva.
136. ¿Qué es la herencia múltiple?
La herencia múltiple es la herencia de una clase a partir de varias clases base.
class A: ...
class B: ...
class C(A, B): ...Da flexibilidad, pero exige disciplina en el diseño de métodos y en super().
En resumen:
- Una clase puede heredar comportamiento de varias fuentes.
- Es útil para el enfoque basado en mixins.
- Puede hacer más difícil entender el MRO.
137. ¿Cómo funciona el MRO (Method Resolution Order) en la herencia múltiple?
El MRO define el orden de búsqueda de métodos en la jerarquía de clases. En Python se usa el algoritmo de linealización C3.
Diamond Problem (problema del diamante): es el caso clásico en el que la
clase D hereda de B y C, y ambas heredan de A. El MRO garantiza que
A se revisará solo después de que se hayan revisado todos sus descendientes
(B y C).
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.mro())
# [D, B, C, A, object]El orden puede verse mediante ClassName.__mro__ o ClassName.mro().
En resumen:
- El MRO decide de qué clase base tomar un método.
- El orden es predecible y está definido formalmente (C3).
- Gracias al MRO, Python maneja correctamente el problema del diamante.
- Para la herencia cooperativa, todas las clases deben llamar a
super().
138. ¿Cuáles son las ventajas y desventajas de usar herencia múltiple?
Ventajas:
- reutilización de comportamiento desde varias fuentes;
- mixins cómodos para "añadir" capacidades.
Desventajas:
- MRO más complejo;
- riesgo de conflictos de nombres o comportamiento;
- depuración e incorporación más difíciles.
En resumen:
- La herencia múltiple es potente, pero exige reglas de diseño estrictas.
- Para la mayoría de casos, la composición es más simple.
- Usa herencia múltiple sobre todo para mixins pequeños.
139. ¿Qué son los mixins?
Un mixin es una clase pequeña con comportamiento adicional y específico, pensada para combinarse mediante herencia y no para usarse por sí sola.
Ejemplos: TimestampMixin, JsonSerializableMixin.
En resumen:
- Un mixin añade una capacidad concreta.
- Normalmente no tiene su propio ciclo de vida completo.
- Encaja bien con la herencia múltiple.
140. ¿Qué es la encapsulación en Python?
La encapsulación es ocultar la implementación interna detrás de una API pública
estable. En Python se implementa principalmente con convenciones y property,
no con modificadores de acceso rígidos.
En resumen:
- El código cliente trabaja con la interfaz, no con los detalles.
- La encapsulación reduce el acoplamiento entre componentes.
- Facilita cambiar la implementación interna sin romper la API.
141. ¿Cuál es la diferencia entre acceso público, privado y protegido?
En Python esto son principalmente convenciones de nombres:
public:name-> accesible en todas partes;protected:_name-> uso interno por convención;private:__name-> cambio interno de nombre (_ClassName__name), no protección absoluta.
En resumen:
- Python no tiene modificadores de acceso estrictos como Java o C#.
_namey__nameson señales de intención para desarrolladores.- El control real de acceso se construye mediante diseño de API.
142. ¿Qué es el polimorfismo y cómo se implementa en Python?
El polimorfismo es la posibilidad de trabajar con distintos objetos a través de una interfaz común. En Python suele implementarse con duck typing y protocolos.
def render(obj) -> str:
return obj.to_text()Cualquier objeto con un método to_text sirve.
En resumen:
- Una interfaz, muchas implementaciones.
- En Python, el polimorfismo suele ser conductual, no jerárquico.
- Esto simplifica ampliar el sistema con nuevos tipos.
143. ¿Qué es la abstracción en Python?
La abstracción consiste en destacar la API esencial y ocultar detalles innecesarios de implementación. El cliente trabaja con el contrato, no con los pasos internos.
En resumen:
- La abstracción reduce la carga cognitiva.
- Facilita sustituir la implementación sin cambiar el código cliente.
- Se implementa mediante interfaces, ABC, protocolos y fachadas.
144. ¿Cómo implementar abstracción de datos?
Enfoque:
- ocultar el acceso directo a campos internos (
_field); - ofrecer una API controlada mediante métodos o
@property; - validar invariantes en la lógica del setter.
class Temperature:
def __init__(self, celsius: float) -> None:
self.celsius = celsius
@property
def celsius(self) -> float:
return self._celsius
@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError("invalid temperature")
self._celsius = valueEn resumen:
- La abstracción de datos protege los invariantes del objeto.
propertyda acceso controlado al estado.- Los detalles internos pueden cambiarse sin cambiar la API.
145. ¿Qué son ABC y `@abstractmethod`?
ABC (Abstract Base Class) es una clase base abstracta del módulo abc.
@abstractmethod marca un método que la subclase debe implementar
obligatoriamente.
from abc import ABC, abstractmethod
class Storage(ABC):
@abstractmethod
def save(self, data: bytes) -> None:
...En resumen:
- ABC formaliza el contrato de la jerarquía.
@abstractmethodimpide instanciar una implementación incompleta.- Es útil para arquitecturas extensibles o basadas en complementos.
146. ¿Qué es property en Python y cómo se usa?
property convierte métodos de acceso en una API similar a atributos, con
posibilidad de validación, cálculos o lógica perezosa.
class User:
def __init__(self, name: str) -> None:
self._name = name
@property
def name(self) -> str:
return self._nameEn resumen:
propertyda control de acceso sin cambiar la sintaxis externa.- Sirve para validación y valores derivados.
- Permite evolucionar la API sin romper a los clientes.
147. ¿Qué es `@property`?
@property es un decorador para un método getter. Junto con @x.setter y
@x.deleter, forma un atributo gestionado.
En resumen:
@propertypermite leer un método como si fuera un campo.- Ayuda a encapsular la implementación interna.
- A menudo se usa para APIs retrocompatibles.
148. ¿Qué es un descriptor en Python?
Un descriptor es un objeto que implementa __get__, __set__ o __delete__
y controla el acceso a atributos de otra clase.
A través de descriptores funcionan property, classmethod y staticmethod.
En resumen:
- Un descriptor es un mecanismo de bajo nivel para acceso a atributos.
- Permite reutilizar lógica de validación o proxy de campos.
- Es la base de muchas técnicas de metaprogramación en Python.
149. ¿Cuál es la diferencia entre property y descriptor?
property es un descriptor de alto nivel ya preparado para un atributo. Un
descriptor personalizado es un mecanismo más general que puede reutilizarse en
muchos campos o clases.
En resumen:
propertyes más simple y local.- Un descriptor es más flexible y escalable.
propertyestá construido sobre el protocolo descriptor.
150. ¿Cuándo conviene usar property y cuándo un descriptor?
Usa property cuando la lógica afecta a uno o dos campos de una clase
concreta. Usa un descriptor cuando la misma lógica (validación, casting,
inicialización perezosa) debe reutilizarse en muchas clases.
En resumen:
- Lógica local de un campo ->
property. - Reutilización de la política de acceso -> descriptor.
- Un descriptor es ventajoso en modelos de dominio grandes.
151. ¿Para qué se usan `setattr()`, `getattr()` y `hasattr()`? ¿Cuál es la diferencia entre ellos?
Son funciones de acceso dinámico a atributos:
getattr(obj, name[, default])-> leer un atributo;setattr(obj, name, value)-> establecer un atributo;hasattr(obj, name)-> comprobar si existe.
value = getattr(user, "email", None)
if not hasattr(user, "active"):
setattr(user, "active", True)En resumen:
getattr/setattr/hasattrsirven para trabajar dinámicamente con objetos.- Son útiles en código genérico, serialización y adaptadores.
- Es importante no abusar de ellas para no perder legibilidad.
152. Explique el significado del método `__set_name__` en los descriptores de Python y dé un ejemplo de uso.
__set_name__(self, owner, name) se llama durante la creación de la clase y
permite que el descriptor conozca el nombre del atributo al que está vinculado.
class Field:
def __set_name__(self, owner, name): self.name = nameEn resumen:
__set_name__inicializa el descriptor con el contexto de la clase.- Permite crear validadores de campos reutilizables.
- Se ejecuta una sola vez en la etapa de creación de la clase.
153. ¿Qué es `dataclass` y cuándo conviene usarlo?
@dataclass genera automáticamente código repetitivo (__init__, __repr__,
__eq__). Es adecuado para modelos de datos sin comportamiento complejo.
from dataclasses import dataclass
@dataclass(slots=True)
class User:
name: str
active: bool = TrueEn resumen:
dataclassreduce código en modelos de datos.- Es una buena opción para DTO y configuraciones.
- Para validación compleja, a menudo hacen falta otras herramientas.
154. ¿Cuál es la diferencia entre `dataclass` y Pydantic?
dataclass se centra en describir la estructura de forma cómoda. Pydantic
añade validación en tiempo de ejecución, análisis y serialización de datos.
En resumen:
dataclasses más ligero y rápido para modelos internos.- Pydantic es mejor para entrada externa y APIs.
- La elección depende de la necesidad de validación en ejecución.
155. ¿Qué son las anotaciones de tipos y para qué sirven?
Las anotaciones de tipos son anotaciones en firmas y variables que forman un contrato explícito. Mejoran las sugerencias del IDE y el análisis estático.
En resumen:
- Las anotaciones de tipos aumentan la claridad de la API.
- Permiten detectar antes cierta clase de errores.
- Son útiles incluso en un lenguaje dinámico.
156. ¿Cómo funciona la comprobación estática de tipos (`mypy`)?
mypy lee anotaciones de tipos y analiza el código sin ejecutarlo, comprobando la
compatibilidad de tipos. Detecta errores de integración antes de la ejecución.
En resumen:
mypyrealiza una comprobación parecida al tiempo de compilación para Python.- Funciona mejor en CI.
- Los modos estrictos reducen la cantidad de bugs en producción.
157. ¿Cómo garantizar seguridad de tipos en un proyecto Python?
- añadir anotaciones de tipos de forma consistente en la API pública;
- activar
mypy/pyrighten CI; - usar
TypedDict,Protocoly genéricos; - minimizar
Anyy los castings implícitos.
En resumen:
- La seguridad de tipos es un proceso, no una acción puntual.
- El mayor efecto lo da una puerta de CI para tipos.
- Aumentar el rigor de forma gradual funciona mejor que un "big bang".
158. ¿Qué es `TypedDict`?
TypedDict describe una forma tipada de diccionario: qué claves se esperan y
de qué tipo son sus valores.
from typing import TypedDict
class UserPayload(TypedDict): name: str; active: boolEn resumen:
TypedDicttipa undictcon claves fijas.- Es cómodo para estructuras parecidas a JSON.
- Lo comprueba un analizador estático.
159. ¿Qué es `Protocol` en typing?
Protocol describe un contrato conductual (tipado estructural): un tipo es
compatible si tiene los métodos o atributos necesarios, independientemente de la
herencia.
En resumen:
Protocolimplementa duck typing en el tipado estático.- Reduce el acoplamiento rígido a clases concretas.
- Es útil para APIs testeables y extensibles.
160. ¿Qué son los genéricos en Python?
Los genéricos permiten escribir tipos y funciones parametrizados por otros tipos.
En la sintaxis moderna: class Box[T]: ..., def first[T](...) -> T.
En resumen:
- Los genéricos hacen que los tipos sean reutilizables.
- Refuerzan la seguridad de tipos de colecciones y contenedores.
- Reducen la duplicación de código tipado.
161. ¿Qué es Pydantic y para qué se usa?
Pydantic es una biblioteca para describir esquemas de datos con validación en tiempo de ejecución, conversión de tipos y serialización cómoda.
Casos típicos: modelos de solicitud y respuesta de FastAPI y configuración de la aplicación.
En resumen:
- Pydantic valida datos externos durante la ejecución.
- Es cómodo para APIs e integraciones.
- Proporciona errores de validación claros y esquemas.
162. ¿Qué son las exceptions en Python?
Una exception es un objeto que señala una situación errónea o excepcional durante la ejecución del código.
En resumen:
- Las exceptions interrumpen el flujo normal.
- Deben manejarse donde se pueda tomar una decisión.
- Un modelo correcto de errores aumenta la fiabilidad del sistema.
163. ¿Cuáles son los tres tipos de errores en Python y en qué se diferencian?
- Syntax errors: errores de sintaxis antes de la ejecución;
- Excepciones en tiempo de ejecución: errores durante la ejecución;
- Logical errors: el código se ejecuta, pero el resultado es incorrecto.
En resumen:
- Los errores de sintaxis y de ejecución los detecta el intérprete.
- Los errores lógicos se detectan con pruebas y revisión.
- Cada tipo requiere un enfoque de diagnóstico distinto.
164. ¿Cómo usar `try`, `except`, `else` y `finally`?
try contiene una operación de riesgo, except maneja el error, else se
ejecuta cuando no hubo error y finally se ejecuta siempre (limpieza).
try: data = load()
except FileNotFoundError: data = {}
else: validate(data)
finally: close_connections()En resumen:
- Usa
elsepara el código de la ruta exitosa. - Usa
finallypara recursos y limpieza. - Captura exceptions concretas, no "todo".
165. ¿Qué significa el orden de las categorías `except`?
Los except se comprueban de arriba abajo, así que primero se colocan las
exceptions más concretas y las generales (Exception) al final.
En resumen:
- El orden de
exceptinfluye en qué manejador se ejecutará. - Un
exceptamplio arriba "se come" los casos específicos. - Esto es crítico para un flujo de recuperación correcto.
166. ¿Cuál es el propósito de la palabra clave `assert` en código Python?
assert comprueba un invariante y lanza AssertionError si la condición es
falsa. Sirve para comprobaciones internas del desarrollador, no para validar
entrada de usuario.
En resumen:
assertdocumenta "esto debe ser verdadero".- No sustituye el manejo de errores en producción.
- Úsalo para contratos en la lógica interna.
167. ¿En qué se diferencia `raise` de simplemente imprimir un mensaje de error en Python?
raise cambia el flujo de control y señala el error al llamador. print solo
muestra texto y no detiene el escenario erróneo.
En resumen:
raisees un mecanismo de manejo de errores;print, no.- Las exceptions pueden capturarse y registrarse de forma centralizada.
printsirve para diagnóstico, no para contratos de error.
168. ¿Cómo crear una excepción propia?
Crea una clase que herede de Exception (o de una excepción base más específica).
class InvalidOrderError(Exception):
passEn resumen:
- Una excepción propia hace que los errores sean expresivos para el dominio.
- Permite capturar con precisión los casos necesarios.
- Es mejor que usar
ValueErrorpara todo.
169. ¿Qué valor tiene crear excepciones propias en Python y cómo mejoran el manejo de errores?
Las exceptions propias forman un modelo de errores explícito del dominio y separan los fallos técnicos de las reglas de negocio.
En resumen:
- Mejoran la legibilidad y el mantenimiento de los manejadores.
- Aportan una semántica más precisa en logs y APIs.
- Simplifican las pruebas de escenarios negativos.
170. ¿Cómo están organizadas las excepciones en Python y cuál es la jerarquía de sus clases?
La jerarquía parte de BaseException. En código de aplicación casi siempre se
trabaja con descendientes de Exception.
Ramas principales: ValueError, TypeError, KeyError, OSError, RuntimeError.
En resumen:
- Las excepciones forman un árbol de clases.
- Es mejor capturar subclases concretas.
BaseExceptionnormalmente no se captura en la lógica de negocio.
171. ¿Qué enfoques hay para manejar varias excepciones distintas en Python y por qué conviene tratarlas por separado?
Enfoques:
exceptseparados para cada tipo;- agrupación de casos lógicamente equivalentes:
except (A, B):; - estrategias de recuperación distintas para cada categoría.
En resumen:
- Distintas excepciones suelen requerir acciones diferentes.
- El manejo separado reduce defectos ocultos.
- Los logs se vuelven más precisos y útiles.
172. ¿Para qué sirve relanzar una excepción en Python y cuándo es útil?
Relanzar (raise sin argumentos dentro de except) permite añadir contexto
(logs, métricas, limpieza) y propagar el mismo error hacia arriba.
En resumen:
- El relanzamiento conserva el traceback original.
- Es útil para un manejo centralizado en niveles superiores.
- No "tragues" errores críticos sin necesidad.
173. ¿Por qué conviene limitar el uso de bloques try-except en programas Python y cómo afecta eso al rendimiento?
try/except es necesario, pero no debería envolver bloques grandes "por si
acaso". Es especialmente costoso cuando las excepciones ocurren con frecuencia
(flujo guiado por excepciones).
En resumen:
- Captura solo errores esperados en un punto estrecho.
- Las excepciones frecuentes empeoran el rendimiento y la legibilidad.
- Prefiere comprobaciones explícitas cuando sea adecuado.
174. ¿Cómo manejar errores al trabajar con archivos?
Usa with y captura excepciones concretas (FileNotFoundError,
PermissionError, UnicodeDecodeError, OSError).
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
...En resumen:
withgarantiza el cierre del archivo.- Maneja errores concretos de entrada y salida.
- Registra el contexto: ruta, modo y codificación.
175. ¿Qué modos de trabajo con archivos existen en Python?
Modos principales:
rlectura;wsobrescritura;aanexar al final;xcreación de un archivo nuevo;bmodo binario;tmodo texto (por defecto);+lectura y escritura.
En resumen:
- El modo define la semántica de seguridad y cambio del archivo.
wborra el contenido;aconserva lo existente.- Para datos binarios, usa
b.
176. ¿Por qué es importante cerrar archivos después de las operaciones y qué puede pasar si se dejan abiertos?
Un archivo abierto mantiene un descriptor del sistema operativo. Si no se cierra:
- fuga de descriptores de archivo;
- bloqueos de archivos o vaciado incompleto del búfer;
- inestabilidad en procesos largos.
En resumen:
- Cerrar un archivo libera recursos del sistema operativo.
withautomatiza un cierre seguro.- Esto es crítico para servicios y scripts por lotes.
177. ¿Cuál es la diferencia entre `read()`, `readline()` y `readlines()` al leer archivos?
read()lee todo el archivo, onbytes o caracteres;readline()lee una sola línea;readlines()lee todas las líneas en una lista.
En resumen:
read()yreadlines()pueden ser pesados en memoria.- Para archivos grandes, es mejor iterar con
for line in file. - La elección depende del volumen y del escenario de procesamiento.
178. ¿Cómo escribir datos en un archivo en Python y cuál es la diferencia entre `w` (escritura) y `a` (anexar)?
Escritura:
with open("out.txt", "w", encoding="utf-8") as f:
f.write("hello\n")w sobrescribe el archivo desde el principio; a añade contenido nuevo al final.
En resumen:
wborra los datos antiguos.aconserva el contenido existente.- Para registros, normalmente se usa
a.
179. ¿Cómo trabajar de forma eficiente con archivos grandes?
- leer en flujo, por líneas o por bloques;
- evitar cargar todo el archivo en memoria;
- usar búferes y generadores;
- para datos columnares o tabulares, elegir formatos y analizadores adecuados.
En resumen:
- La clave es la lectura en flujo en lugar de la lectura completa.
- Los generadores reducen el consumo de memoria.
- El algoritmo de procesamiento importa más que las microoptimizaciones.
180. ¿Qué son los gestores de contexto?
Un gestor de contexto administra un recurso mediante el protocolo
__enter__/__exit__: apertura o inicialización y finalización o limpieza
garantizada.
En resumen:
- Proporciona un ciclo de vida seguro del recurso.
- Funciona mediante
with. - Reduce la cantidad de fugas de recursos.
181. ¿Cómo funciona la construcción `with`?
with llama a __enter__ al entrar en el bloque y a __exit__ al salir,
incluso si ocurre un error.
with open("data.txt", "r", encoding="utf-8") as f:
data = f.read()En resumen:
withgarantiza la limpieza.- Hace que el trabajo con recursos sea declarativo.
- Se recomienda para archivos, bloqueos, transacciones y sesiones.
182. ¿Cómo crear un gestor de contexto propio?
Hay dos enfoques:
- una clase con
__enter__y__exit__; - una función con
contextlib.contextmanager.
from contextlib import contextmanager
@contextmanager
def temp_flag():
yieldEn resumen:
- Una clase sirve para estado complejo.
contextmanageres cómodo para escenarios cortos.- Ambos garantizan la limpieza.
183. ¿Cómo gestiona Python la memoria?
CPython usa conteo de referencias y un recolector cíclico de basura. Además,
tiene un asignador interno (pymalloc) para objetos pequeños.
En resumen:
- El mecanismo básico es el conteo de referencias.
- El GC elimina referencias cíclicas.
- La memoria no siempre se libera de inmediato a nivel del sistema operativo.
184. ¿Qué es el conteo de referencias en Python y por qué es importante para la gestión de memoria?
Cada objeto tiene un contador de referencias. Cuando llega a cero, el objeto puede liberarse. Esto permite liberar rápidamente la mayoría de objetos de vida corta.
En resumen:
- El conteo de referencias da un ciclo de vida predecible a los objetos.
- No resuelve por sí solo las referencias cíclicas.
- Junto con el GC, forma un modelo completo de gestión de memoria.
185. ¿Qué es la recolección de basura en Python?
La recolección de basura en CPython encuentra y elimina ciclos inalcanzables de objetos que no pueden limpiarse solo con conteo de referencias.
En resumen:
- El GC complementa el conteo de referencias.
- Es especialmente importante para grafos cíclicos de referencias.
- Puede controlarse mediante el módulo
gc.
186. ¿Cómo obtener la dirección de memoria de un objeto en Python?
En CPython, id(obj) suele corresponder a la dirección de memoria del objeto
(como un entero). Es una herramienta de diagnóstico, no un identificador
externo estable.
En resumen:
id()da la identidad del objeto.- En CPython suele ser la dirección de memoria.
- No lo uses en lógica de negocio.
187. ¿Para qué sirve la función `getrefcount()` del módulo `sys` y cómo funciona en Python?
sys.getrefcount(obj) devuelve el contador actual de referencias de un objeto
(en CPython). El valor suele ser 1 mayor por la referencia temporal del argumento.
En resumen:
- Es una herramienta de diagnóstico del comportamiento de memoria.
- Es útil para analizar fugas de referencias.
- No debe usarse como base en lógica de aplicación.
188. Explique con ejemplos la diferencia entre objetos mutables e inmutables en relación con el conteo de referencias y la gestión de memoria.
Los objetos inmutables no cambian, así que una "modificación" crea un objeto nuevo. Los objetos mutables cambian en el lugar, y todas las referencias ven el cambio.
a = "x"; b = a; a += "y" # nuevo objeto
x = [1]; y = x; y.append(2) # cambio del mismo objetoEn resumen:
- Los inmutables reducen efectos secundarios.
- Los mutables son más eficientes para modificaciones en el lugar.
- La diferencia es crítica para copiado y referencias compartidas.
189. ¿Cuál es la diferencia entre copia superficial y copia profunda en Python, y cuándo conviene usar cada enfoque?
La copia superficial copia solo el contenedor externo; los objetos anidados siguen siendo compartidos. La copia profunda copia recursivamente toda la estructura.
import copy
copy.copy(obj)
copy.deepcopy(obj)En resumen:
- La copia superficial es suficiente para estructuras "planas".
- La copia profunda hace falta para trabajar de forma aislada con datos mutables anidados.
- La copia profunda es más costosa en tiempo y memoria.
190. ¿Qué son los módulos y paquetes en Python?
Un módulo es un archivo .py independiente con código. Un paquete es un
directorio de módulos (espacio de nombres) que organiza el código en bloques
más grandes.
En resumen:
- Módulo = unidad de código.
- Paquete = estructura para escalar módulos.
- Dividir en módulos y paquetes mejora el mantenimiento.
191. ¿Cómo importar un módulo en Python?
Formas principales:
import module;import module as m;from module import name;from package import submodule.
En resumen:
- La importación carga un módulo y lo hace disponible en el espacio de nombres.
- Un alias (
as) mejora la legibilidad y evita conflictos. - Conviene preferir importaciones explícitas.
192. ¿Qué tipos de imports existen?
Tipos habituales:
- absolutos;
- relativos;
- selectivos (
from x import y); - con alias (
as); - dinámicos (
importlib).
En resumen:
- El tipo de importación influye en la legibilidad y el mantenimiento.
- En producción suelen usarse importaciones absolutas.
- Las importaciones dinámicas se aplican de forma puntual.
193. ¿Cómo funciona el sistema de importaciones?
El intérprete busca el módulo en sys.path, lo carga una vez y lo almacena en
la caché de sys.modules. Una importación repetida usa esa caché.
En resumen:
- Importar = búsqueda + ejecución del módulo + almacenamiento en caché.
- Esto explica por qué el código del módulo se ejecuta en la primera importación.
- Las importaciones cíclicas aparecen por el orden de inicialización de módulos.
194. ¿Cuál es la diferencia entre importación absoluta y relativa?
La importación absoluta empieza desde la raíz del paquete (from app.utils import x).
La importación relativa usa puntos (from .utils import x).
En resumen:
- Las importaciones absolutas son más legibles y estables.
- Las relativas son cómodas dentro del paquete, pero peores para refactorizar.
- En proyectos grandes, conviene usar absolutas por defecto.
195. ¿Qué hace la función `dir()` y cómo puede aplicarse a módulos?
dir(obj) devuelve una lista de atributos o nombres disponibles del objeto.
Para módulos, es una forma rápida de inspeccionar la API.
import math
dir(math)En resumen:
dir()ayuda en la exploración interactiva de módulos.- Es útil en REPL y depuración.
- No sustituye la documentación oficial.
196. Explique el papel de `if __name__ == "__main__":` en scripts de Python.
Ese bloque se ejecuta solo cuando el archivo se lanza como script y no cuando se importa como módulo. Esto separa el código reutilizable del punto de entrada de CLI.
En resumen:
- Permite tanto ejecutar el módulo como importarlo.
- Evita ejecución no deseada durante la importación.
- Es el patrón estándar para scripts.
197. ¿Qué significa el archivo `__init__.py` en un paquete Python?
__init__.py marca un directorio como paquete y puede inicializar la API a
nivel de paquete, por ejemplo, reexportando clases o funciones.
En resumen:
- Define la interfaz pública del paquete.
- Puede contener una inicialización mínima.
- No conviene sobrecargarlo con lógica pesada.
198. ¿Qué buenas prácticas existen para el estilo de importaciones en código Python?
- agrupar importaciones: biblioteca estándar, terceros y locales;
- evitar
from x import *; - mantener las importaciones al principio del archivo, salvo casos justificados de importación diferida;
- usar ordenación y formateo (
ruff,isort).
En resumen:
- Las importaciones consistentes mejoran la legibilidad.
- Las importaciones explícitas reducen conflictos de nombres.
- La automatización con herramientas elimina errores manuales.
199. ¿Qué módulos integrados populares existen en Python?
Ejemplos de módulos populares de la biblioteca estándar:
os,sys,pathlib,json,datetime,re,collections,itertools,functools,typing,asyncio,subprocess,logging,argparse.
En resumen:
- La biblioteca estándar cubre la mayoría de tareas básicas.
- Esto reduce la cantidad de dependencias externas.
- Conviene conocer bien la biblioteca estándar antes de añadir paquetes de terceros.
200. ¿Qué sabe sobre el paquete `collections` y qué otros módulos integrados se han usado?
collections proporciona estructuras de alto nivel:
Counter,defaultdict,deque,namedtuple,ChainMap.
Suele combinarse con:
itertoolspara pipelines iterativos;functoolspara caché y utilidades funcionales;pathlib,json,datetimeen tareas de aplicación.
En resumen:
collectionsamplía los contenedores básicos con estructuras prácticas.- A menudo mejora la simplicidad y el rendimiento del código.
- Funciona bien junto con
itertoolsyfunctools.
201. ¿Qué devuelve `sys.argv`?
sys.argv devuelve una lista de argumentos de la línea de comandos:
sys.argv[0]-> nombre del script;- el resto de elementos -> argumentos pasados.
En resumen:
sys.argves la interfaz básica de argumentos de línea de comandos.- Los valores llegan como cadenas.
- Para CLIs complejas, es mejor
argparse.
202. ¿Cuál es el módulo principal para trabajar con el sistema operativo en Python?
El módulo principal es os junto con os.path, y para una API moderna de
rutas se recomienda pathlib.
En resumen:
osda acceso a la API de procesos, entorno y sistema de archivos del SO.pathlibes más cómodo para trabajar con rutas.- En proyectos reales, a menudo se usan ambos.
203. ¿Cómo mezclar elementos de una lista usando el módulo `random`?
Usa random.shuffle(list_) para mezclar en el lugar.
import random
items = [1, 2, 3, 4]
random.shuffle(items)En resumen:
shufflemodifica la lista original.- Funciona solo con secuencias mutables, por ejemplo listas.
- Para una copia nueva:
random.sample(items, k=len(items)).
204. ¿Qué es un entorno virtual?
Un entorno virtual es un entorno Python aislado con sus propios paquetes y versiones de dependencias para un proyecto concreto.
En resumen:
- El aislamiento elimina conflictos de dependencias entre proyectos.
- La herramienta estándar es
venv. - Es una práctica básica para un desarrollo reproducible.
205. ¿Cómo funciona `pip`?
pip instala, actualiza y elimina paquetes desde índices, normalmente PyPI,
resuelve dependencias y los instala en el entorno actual.
En resumen:
pipes el gestor de paquetes estándar de Python.- Funciona dentro del intérprete o venv activo.
- Para estabilidad, son importantes las versiones fijadas.
206. ¿Qué es `requirements.txt`?
requirements.txt es un archivo con la lista de dependencias, a menudo con
versiones exactas, que se usa para una instalación reproducible.
En resumen:
- El formato es simple y compatible con
pip install -r. - Lo mejor es fijar versiones exactas para producción.
- A menudo lo generan herramientas como
pip-toolsopoetry exporta partir de listas declarativas o archivos de bloqueo.
207. ¿Qué es `pyproject.toml` y por qué se convirtió en estándar?
pyproject.toml es un archivo estandarizado de configuración del proyecto:
metadatos del paquete, sistema de compilación y configuración de herramientas como
ruff, pytest, mypy, etc.
En resumen:
- Centraliza la configuración del proyecto Python.
- Lo soportan las herramientas modernas de packaging.
- Reduce la cantidad de archivos de configuración dispersos.
208. ¿Cómo gestionar dependencias en un proyecto Python moderno?
- aislar el entorno con
venv; - definir dependencias en
pyproject.toml; - fijar archivos de bloqueo o restricciones para reproducibilidad;
- actualizar regularmente con comprobaciones en CI.
En resumen:
- La gestión de dependencias debe ser reproducible.
- No mezcles paquetes globales y del proyecto.
- Haz las actualizaciones de forma controlada mediante pruebas.
209. ¿Cómo organizar correctamente la estructura de un proyecto Python grande?
- dividir el código en paquetes de dominio;
- tener capas separadas:
api,services,domain,infrastructure; - separar
tests/,scripts/,configs/; - mantener límites claros entre módulos y una API pública explícita.
En resumen:
- La estructura debe reflejar el dominio, no detalles técnicos accidentales.
- Los límites claros reducen dependencias cíclicas.
- Las pruebas y las herramientas deben ser una parte de primera clase del árbol.
210. ¿Cuál es la diferencia entre las pruebas automatizadas y las manuales, y cuáles son las ventajas de las pruebas automatizadas?
Las pruebas manuales las realiza una persona paso a paso. Las pruebas automatizadas las ejecutan scripts o marcos de pruebas.
En resumen:
- Las pruebas automáticas son rápidas, repetibles y aptas para CI.
- Las pruebas manuales son útiles para escenarios exploratorios de UX.
- En producción hace falta una combinación de ambos enfoques.
211. ¿Qué es TDD (Test-Driven Development)?
TDD es un ciclo: escribir una prueba que falla -> implementación mínima -> refactorización.
En resumen:
- TDD moldea la API a través de pruebas.
- Da retroalimentación rápida sobre regresiones.
- Funciona mejor para lógica de negocio modular.
212. ¿Qué marcos de pruebas populares existen en Python?
Los más populares son pytest, unittest de la biblioteca estándar y también
hypothesis para pruebas basadas en propiedades.
En resumen:
pytestes la opción más habitual en proyectos nuevos.unittestes útil como herramienta base estándar.hypothesisrefuerza la cobertura de casos límite.
213. ¿Qué es `unittest` en Python?
unittest es el marco xUnit integrado para pruebas: clases de caso de prueba,
métodos de aserción, preparación/limpieza y ejecutor de pruebas.
En resumen:
- Forma parte de la biblioteca estándar.
- Encaja en entornos conservadores sin dependencias externas.
- Tiene una sintaxis más ceremonial que
pytest.
214. ¿Qué es `pytest` y en qué se diferencia de `unittest` por sintaxis y funcionalidad?
pytest es un marco con sintaxis simple para pruebas funcionales, fixtures
potentes, parametrización y un ecosistema de complementos.
En resumen:
pytestrequiere menos código repetitivo y es más cómodo para suites grandes.unittestestá basado en clases y es estándar en la biblioteca estándar.- En proyectos modernos suele dominar
pytest.
215. Describa cómo se usa `pytest.raises` para comprobar que aparece una excepción concreta en código Python.
pytest.raises(ExpectedError) comprueba que un bloque de código lanza
exactamente la excepción esperada.
import pytest
with pytest.raises(ValueError):
int("abc")En resumen:
- Prueba escenarios negativos de forma explícita.
- Protege contra el paso silencioso de errores.
- También puede verificar el mensaje o atributos de la excepción.
216. ¿Qué es la parametrización en pruebas y cómo la soporta pytest con `@pytest.mark.parametrize`?
La parametrización ejecuta una prueba con varios conjuntos de datos de entrada. En
pytest se hace con el decorador @pytest.mark.parametrize.
En resumen:
- Menos duplicación de código de prueba.
- Facilita escalar casos.
- Da más transparencia sobre la cobertura de escenarios de entrada.
217. ¿Cómo definir nombres propios de pruebas parametrizadas en pytest para mejorar la legibilidad?
Usa el parámetro ids en parametrize.
@pytest.mark.parametrize("value,expected", [(2, True), (3, False)], ids=["even", "odd"])En resumen:
idshace más clara la salida de la ejecución de pruebas.- Facilita diagnosticar fallos.
- Es especialmente útil con muchos casos.
218. ¿Qué es Arrange/Setup en pruebas y por qué es importante?
Arrange/Setup es la preparación del estado de prueba: datos, objetos, mocks y entorno. Una buena preparación hace que la prueba sea determinista.
En resumen:
- Una preparación inestable produce pruebas inestables.
- Una buena preparación aísla la prueba de efectos externos.
- Un Arrange claro mejora la legibilidad del escenario.
219. ¿Qué etapas incluye la limpieza final y por qué es importante?
La fase de limpieza final elimina todo lo que creó la prueba: archivos temporales, conexiones, mocks y registros de prueba en la base de datos.
En resumen:
- La limpieza garantiza aislamiento entre pruebas.
- Reduce efectos secundarios e inestabilidad.
- En pytest es cómodo hacerlo con finalizadores de fixtures o fixtures con
yield.
220. ¿Cuál es la diferencia entre `setUp()` y `setUpClass()` en unittest?
setUp() se ejecuta antes de cada método de prueba. setUpClass()
(classmethod) se ejecuta una vez antes de todas las pruebas de la clase.
En resumen:
setUpsirve para el aislamiento por prueba.setUpClasssirve para recursos compartidos costosos.- Demasiado estado compartido vía
setUpClasspuede complicar las pruebas.
221. ¿Qué es un objeto mock y cómo ayuda a mejorar la calidad de las pruebas?
Un objeto mock es un objeto sustituto que imita una dependencia y permite controlar el comportamiento o verificar llamadas.
En resumen:
- Los mocks aíslan la unidad de servicios externos.
- Hacen las pruebas más rápidas y estables.
- Permiten comprobar interacciones, no solo resultados.
222. ¿Cuál es la diferencia entre usar `mock.patch` en unittest y `monkeypatch` en pytest para mockear objetos?
mock.patch de unittest.mock parchea objetos por ruta de importación y tiene
una API completa para verificar llamadas. monkeypatch en pytest cambia de forma
más simple atributos, variables de entorno o diccionarios durante la prueba.
En resumen:
patches más potente para escenarios con aserciones sobre mocks.monkeypatches cómodo para sustituciones rápidas en pruebas.- A menudo se combinan según el caso.
223. ¿Cuál es el propósito del parámetro `scope` en las fixtures de pytest?
scope define el ciclo de vida de una fixture: function, class, module,
package, session.
En resumen:
- Un alcance menor da mejor aislamiento.
- Un alcance mayor acelera la ejecución de suites grandes.
- Elegir el alcance es un equilibrio entre velocidad e independencia.
224. ¿Qué es la complejidad algorítmica y cómo se determina?
La complejidad algorítmica evalúa cómo crecen los costes de tiempo y memoria al
aumentar el tamaño de los datos de entrada n.
En resumen:
- Se mide la complejidad temporal y espacial.
- La evaluación suele ser asintótica.
- Ayuda a elegir algoritmos y estructuras de datos.
225. Explique la notación Big O y su importancia para evaluar la complejidad de algoritmos.
Big O describe el límite superior del crecimiento del coste de un algoritmo con
n grande, ignorando constantes y términos menores.
En resumen:
- Big O muestra escalabilidad, no tiempo exacto.
- Proporciona un lenguaje para comparar algoritmos.
- Es crítica para el rendimiento con grandes volúmenes.
226. ¿Qué tipos de complejidad algorítmica aparecen con más frecuencia?
Las más comunes son O(1), O(log n), O(n), O(n log n) y O(n^2).
En resumen:
O(n)yO(n log n)son las más típicas en tareas aplicadas.O(n^2)o peor suele convertirse en cuello de botella.- También hay que considerar memoria, no solo tiempo.
227. Dé ejemplos de tareas que se resuelven eficientemente con algoritmos lineales O(n).
Ejemplos:
- búsqueda de máximo o mínimo;
- filtrado de elementos;
- conteo de frecuencias con
Counter; - comprobación de condiciones para cada elemento.
En resumen:
- O(n) significa una sola pasada sobre los datos.
- Es óptimo para muchas agregaciones.
- Los algoritmos lineales escalan bien.
228. ¿Cómo funciona `lru_cache` y cuándo conviene usarlo?
functools.lru_cache guarda en caché los resultados de una función según sus
argumentos y devuelve el valor ya preparado en llamadas repetidas.
En resumen:
- Es eficaz para funciones puras con entradas repetidas.
- No encaja en funciones con efectos secundarios.
maxsizecontrola el tamaño de la caché en memoria.
229. ¿Cómo perfilar el rendimiento de código Python?
Herramientas básicas:
timeitpara micro-mediciones;cProfile/pstatspara perfiles a nivel de llamadas;py-spy/scalenepara análisis más cercanos a producción.
En resumen:
- Optimiza solo después de medir.
- Perfila escenarios de carga realistas.
- Fija una línea base antes y después de los cambios.
230. ¿Cuáles son las formas principales de optimizar código Python?
- elegir las estructuras de datos correctas;
- reducir la complejidad asintótica;
- evitar copias innecesarias;
- usar generadores o pipelines perezosos;
- almacenar en caché cálculos costosos;
- mover rutas críticas a C, Rust o NumPy si hace falta.
En resumen:
- La mayor mejora la da el algoritmo, no un ajuste sintáctico.
- La optimización debe apoyarse en perfilado.
- No conviene sacrificar legibilidad sin un beneficio medido.
231. ¿Cuándo conviene usar extensiones en C o PyPy?
Las extensiones en C son adecuadas para puntos críticos de CPU muy concretos y para integrarse con bibliotecas nativas. PyPy conviene cuando código Python puro de larga duración se beneficia del JIT.
En resumen:
- Extensión en C: máximo rendimiento a costa de mayor complejidad de compilación.
- PyPy: mejora potencial sin reescribir en C.
- La elección debe basarse en pruebas comparativas de tu carga real.
232. ¿Qué es el perfilado de memoria?
El perfilado de memoria mide dónde y cuánta memoria consume el código con el tiempo para encontrar fugas y zonas pesadas.
En resumen:
- Muestra puntos calientes de memoria y picos.
- Es útil para cargas batch y de datos.
- Herramientas:
tracemalloc,memory_profiler,scalene.
233. ¿Qué es el GIL (Global Interpreter Lock)?
El GIL es un mecanismo de CPython que permite que solo un hilo ejecute bytecode de Python al mismo tiempo dentro de un proceso.
En resumen:
- El GIL afecta al multihilo en tareas intensivas de CPU.
- Para tareas limitadas por I/O, los hilos siguen siendo útiles.
- Para paralelismo de CPU, suele usarse
multiprocessing.
234. ¿Cómo afecta el GIL a la concurrencia en CPython y qué consecuencias tiene para el multithreading?
Debido al GIL, los hilos en CPython no ejecutan bytecode de Python en paralelo real para código intensivo de CPU. Se alternan de forma cooperativa.
Consecuencias:
- para hilos limitados por I/O, el efecto es bueno porque la espera de I/O se solapa;
- para tareas intensivas de CPU, la mejora con hilos suele ser limitada.
En resumen:
- El GIL limita el paralelismo de hilos en escenarios intensivos de CPU.
- Los hilos siguen siendo útiles para red y disco.
- Para CPU, usa procesos o cómputo nativo.
235. ¿Cómo afecta el GIL al rendimiento?
El GIL casi no molesta en tareas limitadas por I/O, pero limita la capacidad de procesamiento de código Python multihilo intensivo de CPU dentro de un proceso.
En resumen:
- El impacto del GIL depende del tipo de carga.
- El código intensivo de CPU con hilos en CPython a menudo no escala.
- La arquitectura debe elegirse según el perfil de tareas.
236. Explique el concepto de threading en Python y en qué se diferencia de multiprocessing.
threading ejecuta varios hilos dentro de un proceso con memoria compartida.
multiprocessing ejecuta procesos separados con memoria separada.
En resumen:
- Los hilos son más ligeros y cómodos para tareas limitadas por I/O.
- Los procesos dan paralelismo real de CPU.
- Los procesos tienen más sobrecarga de IPC y creación.
237. ¿Cuándo usar multiprocessing en lugar de threading?
Cuando la tarea es intensiva de CPU y necesita usar varios núcleos en CPython. Ejemplos: parsing pesado, procesamiento de imagen o vídeo y cálculo numérico.
En resumen:
- Intensiva de CPU -> normalmente
multiprocessing. - Limitada por I/O -> normalmente
threadingoasyncio. - Ten en cuenta el coste de serialización entre procesos.
238. ¿Cuál es la diferencia entre concurrencia y paralelismo y cuándo conviene usar cada uno?
La concurrencia es el solapamiento de tareas en el tiempo. El paralelismo es la ejecución física simultánea de tareas en varios núcleos.
En resumen:
- La concurrencia es útil para latencias de I/O.
- El paralelismo es necesario para cálculos intensivos de CPU.
- En Python, la herramienta depende del tipo de cuello de botella.
239. ¿Qué es la concurrencia en Python?
La concurrencia en Python es la organización de varias tareas para que progresen juntas mediante hilos, asyncio o procesos.
En resumen:
- Trata de gestionar muchas tareas, no necesariamente en paralelo.
- Permite aumentar la capacidad de procesamiento en escenarios de I/O.
- Requiere diseñar bien sincronización y cancelación.
240. ¿Cuál es la diferencia entre tareas limitadas por I/O y tareas intensivas de CPU?
Las tareas limitadas por I/O esperan sobre todo red, disco o base de datos. Las tareas intensivas de CPU gastan sobre todo tiempo en cálculo del procesador.
En resumen:
- Las tareas limitadas por I/O escalan bien con asyncio o hilos.
- Las tareas intensivas de CPU escalan mejor con multiprocessing o código nativo.
- Primero identifica el cuello de botella con perfilado.
241. ¿Para qué sirve el módulo `asyncio` en Python y cómo permite implementar programación asíncrona?
asyncio proporciona un bucle de eventos, planificación de tareas y primitivas asíncronas
para concurrencia cooperativa en tareas limitadas por I/O.
En resumen:
- Permite atender muchas operaciones de I/O de forma eficiente.
- Se basa en
asyncyawait. - Encaja bien en servicios y clientes de red.
242. ¿En qué se diferencian la programación síncrona y la asíncrona en Python?
Síncrono: una llamada bloquea el hilo actual hasta completarse. Asíncrono:
await cede el control al bucle de eventos mientras la operación espera I/O.
En resumen:
- La asincronía reduce el tiempo ocioso durante I/O.
- El modelo síncrono es más simple para lógica lineal.
- La asincronía añade complejidad en gestión de tareas y cancelación.
243. ¿Qué son `async` y `await`?
async def define una corrutina. await pausa la corrutina hasta que
el objeto esperable esté listo y devuelve el control al bucle.
En resumen:
- Es la sintaxis de un modelo asíncrono cooperativo.
- Se usa junto con
asyncioy bibliotecas asíncronas. awaitsolo es válido dentro deasync def.
244. ¿Cómo funciona `asyncio`?
asyncio ejecuta un bucle de eventos que procesa tareas (corrutinas), cambiando en
los puntos await y planificando operaciones I/O listas.
Regla crítica: El bucle de eventos funciona en un solo hilo. Cualquier operación
bloqueante (time.sleep(), peticiones síncronas con requests, cálculos
pesados) detiene todo el ciclo y todas las demás tareas.
En resumen:
- Un hilo puede atender muchas tareas I/O gracias a la cooperación.
- La planificación no expulsa tareas de forma preventiva.
- Las llamadas bloqueantes destruyen el rendimiento de
asyncio.
245. ¿Qué es el bucle de eventos?
El bucle de eventos es un planificador que sigue eventos o disponibilidad de I/O y ejecuta las devoluciones de llamada o corrutinas correspondientes.
En resumen:
- Es el componente central del modelo de
asyncio. - Gestiona el ciclo de vida de las tareas asíncronas.
- Determina cuándo cada corrutina continúa ejecutándose.
246. ¿Cómo permite `asyncio` implementar programación asíncrona y qué componentes principales intervienen en código asyncio?
Componentes clave:
- bucle de eventos;
- corrutina (
async def); - tarea (
asyncio.create_task); - objetos esperables (futures, tareas, corrutinas);
- primitivas de sincronización (
Lock,Queue,Semaphore).
En resumen:
asynciocombina planificación y API asíncrona en un solo modelo.- Las tareas comparten un hilo de ejecución de forma cooperativa.
- La arquitectura debe tener en cuenta tiempos de espera, reintentos y cancelación.
247. ¿Cuándo `asyncio` no aporta ventajas?
Cuando la carga es intensiva de CPU o las bibliotecas principales son bloqueantes y no tienen API asíncrona. Tampoco compensa en scripts simples y cortos.
Solución para código bloqueante: Si necesitas usar una biblioteca bloqueante
en un entorno asíncrono, usa loop.run_in_executor(None, sync_func), que la
ejecutará en un hilo aparte sin bloquear el bucle de eventos.
En resumen:
- La asincronía no acelera cálculos puros.
- Sin bibliotecas I/O no bloqueantes, la ganancia es mínima.
run_in_executorayuda a integrar código heredado o síncrono.- La complejidad asíncrona debe justificarse por la carga.
248. ¿Cómo funciona la cancelación en asyncio?
La cancelación de una tarea (task.cancel()) lanza CancelledError dentro de
la corrutina. El código debe manejar correctamente la limpieza en try/finally.
En resumen:
- La cancelación es un flujo de control normal en código asíncrono.
- Hay que diseñar las corrutinas teniendo en cuenta la cancelación.
- Ignorar la cancelación lleva a tareas colgadas.
249. ¿Qué es `contextvars`?
contextvars proporciona variables locales al contexto, seguras para
escenarios asíncronos e hilos. Es útil para identificador de solicitud,
identificador de correlación y contexto de inquilino.
En resumen:
- Es una alternativa al estado global en código concurrente.
- El valor queda aislado por contexto.
- Mejora el trazado y la observabilidad.
250. ¿Qué buenas prácticas conviene aplicar al escribir código Python?
- seguir PEP 8 y automatizar el formateo;
- escribir anotaciones de tipos explícitas para la API pública;
- usar
withpara recursos; - cubrir la lógica de negocio con pruebas;
- evitar la optimización prematura y perfilar;
- mantener módulos pequeños con responsabilidad clara;
- gestionar dependencias mediante
pyproject.tomly una estrategia de bloqueo.
En resumen:
- La legibilidad y la previsibilidad importan más que los "trucos".
- La calidad se sostiene en la automatización: análisis estático, tipos, pruebas y CI.
- La simplicidad arquitectónica reduce el coste de mantenimiento.