Por qué Python sigue siendo el lenguaje favorito de los recruiters técnicos
Python aparece en más del 60 % de las ofertas de trabajo en data, backend y ML. Los entrevistadores lo usan porque permite evaluar algoritmia, diseño de software y productividad en el mismo ejercicio.
Esta guía cubre las 50 preguntas que aparecen con más frecuencia en entrevistas de empresas tech de LATAM, España y empresas globales en remoto. Cada pregunta incluye la respuesta que esperan, el código que tenés que poder escribir de memoria y los errores comunes que te hacen perder puntos.
Bloque 1: Fundamentos del lenguaje
1. ¿Qué es Python y cuáles son sus características principales?
Python es un lenguaje interpretado, dinámicamente tipado y multiparadigma (soporta programación imperativa, orientada a objetos y funcional). Sus pilares son:
- Sintaxis limpia que prioriza la legibilidad (PEP 8)
- Gestión automática de memoria con garbage collector
- Tipado dinámico fuerte (no implícito —
"3" + 3lanzaTypeError) - Ecosistema gigante: pip, PyPI, y miles de librerías
# Tipado dinámico: la variable puede cambiar de tipo
x = 42
x = "ahora soy string"
x = [1, 2, 3]2. ¿Cuál es la diferencia entre una lista, una tupla y un set?
Esta es la pregunta de calentamiento más común. Memorizá la tabla:
- list — mutable, ordenada, permite duplicados, acceso O(1) por índice
- tuple — inmutable, ordenada, permite duplicados, acceso O(1) por índice
- set — mutable, sin orden, sin duplicados, lookup O(1) promedio
lista = [1, 2, 2, 3] # permite duplicados
tupla = (1, 2, 2, 3) # inmutable
conjunto = {1, 2, 2, 3} # => {1, 2, 3} — elimina duplicados
# Cuándo usar cada uno:
# list → cuando necesitás orden y mutabilidad
# tuple → claves de diccionario, retorno múltiple de funciones
# set → eliminar duplicados, operaciones de intersección/unión rápidas3. ¿Qué es un diccionario y cómo funciona internamente?
Un dict es una tabla hash. Las claves se hashean para determinar el bucket donde se almacena el valor. Desde Python 3.7 los dicts mantienen el orden de inserción.
agenda = {"nombre": "Ana", "edad": 30}
# Acceso seguro — nunca lances KeyError en producción
nombre = agenda.get("nombre", "desconocido")
# Dict comprehension
cuadrados = {n: n**2 for n in range(1, 6)}
# => {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Merge en Python 3.9+
merged = {"a": 1} | {"b": 2} # => {"a": 1, "b": 2}Trampa de entrevista: solo los tipos hashables (inmutables) pueden ser claves: strings, números, tuplas. Las listas NO pueden ser clave.
4. ¿Qué significa que Python sea "pass by object reference"?
Python no es pass-by-value ni pass-by-reference. Es pass-by-object-reference: la función recibe una referencia al objeto, no una copia.
def agregar(lista, elemento):
lista.append(elemento) # modifica el objeto original
lista = ["nueva lista"] # solo reasigna la variable local
mi_lista = [1, 2, 3]
agregar(mi_lista, 4)
print(mi_lista) # => [1, 2, 3, 4]Regla: si el objeto es mutable (list, dict, set) y lo modificás sin reasignar, el cambio se ve afuera.
5. ¿Cuál es la diferencia entre `==` e `is`?
== compara valor. is compara identidad (mismo objeto en memoria).
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True — mismo valor
print(a is b) # False — objetos distintos en memoria
print(a is c) # True — c apunta al mismo objeto que a
# Integer caching (CPython) — gotcha de entrevista
x = 256; y = 256
print(x is y) # True — CPython cachea -5 a 256
x = 257; y = 257
print(x is y) # False — fuera del rango cacheadoNunca uses is para comparar valores. Solo usalo para None: if x is None.
Bloque 2: Funciones y closures
6. ¿Qué es un argumento `*args` y `**kwargs`?
def log(*args, **kwargs):
# *args — tupla con todos los posicionales
# **kwargs — dict con todos los con nombre
print("args:", args)
print("kwargs:", kwargs)
log(1, 2, 3, usuario="Ana", nivel="INFO")
# args: (1, 2, 3)
# kwargs: {"usuario": "Ana", "nivel": "INFO"}7. ¿Qué es una función lambda y cuándo usarla?
Una lambda es una función anónima de una sola expresión.
# Uso real: ordenar por criterio personalizado
personas = [{"nombre": "Carlos", "edad": 25}, {"nombre": "Ana", "edad": 30}]
personas.sort(key=lambda p: p["edad"])
ordenadas = sorted(personas, key=lambda p: p["nombre"])Cuándo NO usar lambda: si necesitás más de una línea o querés reusar la función — usá def con nombre.
8. ¿Qué es un closure?
Un closure es una función que recuerda las variables del scope donde fue creada, aunque ese scope ya no exista.
def hacer_multiplicador(factor):
def multiplicar(numero):
return numero * factor # "factor" está en el closure
return multiplicar
por_tres = hacer_multiplicador(3)
por_cinco = hacer_multiplicador(5)
print(por_tres(10)) # => 30
print(por_cinco(10)) # => 509. ¿Qué es un decorador y cómo se implementa?
Un decorador envuelve a otra función para modificar su comportamiento sin cambiarle el código.
import time, functools
def medir_tiempo(func):
@functools.wraps(func) # preserva __name__, __doc__, etc.
def wrapper(*args, **kwargs):
inicio = time.perf_counter()
resultado = func(*args, **kwargs)
fin = time.perf_counter()
print(f"{func.__name__} tardó {fin - inicio:.4f}s")
return resultado
return wrapper
@medir_tiempo
def procesar(n):
return sum(range(n))
procesar(1_000_000) # => procesar tardó 0.0312s
# Decorador con argumentos
def reintentar(veces=3):
def decorador(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for intento in range(veces):
try:
return func(*args, **kwargs)
except Exception as e:
if intento == veces - 1:
raise
print(f"Intento {intento+1} fallido: {e}")
return wrapper
return decorador10. ¿Cuál es la diferencia entre `@staticmethod` y `@classmethod`?
class Circulo:
PI = 3.14159
def __init__(self, radio):
self.radio = radio
def area(self): # método de instancia — recibe self
return self.PI * self.radio ** 2
@classmethod
def desde_diametro(cls, diametro): # recibe cls — factory method
return cls(diametro / 2)
@staticmethod
def es_valido(radio): # no recibe nada especial
return radio > 0Regla: @classmethod para factories y constructores alternativos. @staticmethod para utilidades que no necesitan estado de instancia ni de clase.
Bloque 3: OOP
11. ¿Cómo funciona la herencia en Python?
class Animal:
def __init__(self, nombre):
self.nombre = nombre
def hablar(self):
raise NotImplementedError
class Perro(Animal):
def hablar(self):
return f"{self.nombre} dice: Guau"
class Gato(Animal):
def hablar(self):
return f"{self.nombre} dice: Miau"
# Polimorfismo
for a in [Perro("Rex"), Gato("Luna")]:
print(a.hablar())12. ¿Qué es el MRO (Method Resolution Order)?
El MRO determina el orden de búsqueda de métodos en herencia múltiple. Usa el algoritmo C3 Linearization.
class A:
def hola(self): return "A"
class B(A):
def hola(self): return "B"
class C(A):
def hola(self): return "C"
class D(B, C):
pass
# MRO: D -> B -> C -> A -> object
print(D().hola()) # => "B"13. ¿Qué son los dunder methods (métodos mágicos)?
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, otro):
return Vector(self.x + otro.x, self.y + otro.y)
def __eq__(self, otro):
return self.x == otro.x and self.y == otro.y
def __hash__(self):
return hash((self.x, self.y))
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # => Vector(4, 6)14. ¿Qué es una dataclass?
from dataclasses import dataclass, field
from typing import List
@dataclass
class Producto:
nombre: str
precio: float
tags: List[str] = field(default_factory=list)
activo: bool = True
p = Producto("Laptop", 999.99, ["tech"])
print(p) # Producto(nombre='Laptop', precio=999.99, tags=['tech'], activo=True)
@dataclass(frozen=True) # inmutable y hashable
class Punto:
x: float
y: float15. ¿Cuál es la diferencia entre `__str__` y `__repr__`?
__repr__: representación inequívoca para desarrolladores. Ideal:eval(repr(obj)) == obj.__str__: representación amigable para usuarios finales.
from datetime import datetime
d = datetime(2024, 1, 15)
print(repr(d)) # datetime.datetime(2024, 1, 15, 0, 0)
print(str(d)) # 2024-01-15 00:00:00
print(f"{d!r}") # usa repr en f-stringsBloque 4: Iteradores, generadores y comprehensions
16. ¿Qué es un iterador y cómo se implementa?
class ContadorHasta:
def __init__(self, maximo):
self.maximo = maximo
self.actual = 0
def __iter__(self):
return self
def __next__(self):
if self.actual >= self.maximo:
raise StopIteration
self.actual += 1
return self.actual
for n in ContadorHasta(5):
print(n) # 1 2 3 4 517. ¿Qué es un generador y por qué es más eficiente?
Un generador usa yield. No carga todos los valores en memoria — los produce uno por uno (lazy).
def numeros_gen(n):
for i in range(n):
yield i ** 2
# Para n=1_000_000:
# lista — ~8 MB | generador — ~200 bytes
gen = numeros_gen(5)
print(next(gen)) # 0
print(next(gen)) # 1
# Generator expression — como list comprehension pero lazy
cuadrados = (x**2 for x in range(1_000_000))18. ¿Qué hace `yield from`?
def aplanar(lista):
for elemento in lista:
if isinstance(elemento, list):
yield from aplanar(elemento)
else:
yield elemento
resultado = list(aplanar([1, [2, [3, 4]], 5]))
# => [1, 2, 3, 4, 5]19. ¿Cuál es la diferencia entre `map()`, `filter()` y list comprehensions?
numeros = [1, 2, 3, 4, 5, 6]
dobles_map = list(map(lambda x: x * 2, numeros))
dobles_comp = [x * 2 for x in numeros] # más pythónico
pares_filter = list(filter(lambda x: x % 2 == 0, numeros))
pares_comp = [x for x in numeros if x % 2 == 0]Preferí comprehensions para casos simples. Usá map/filter cuando la función ya existe (evitás una lambda).
20. ¿Qué es `itertools` y para qué sirve?
import itertools
# chain — concatenar iterables
list(itertools.chain([1, 2], [3, 4])) # => [1, 2, 3, 4]
# product — producto cartesiano
list(itertools.product("AB", [1, 2])) # => [('A',1),('A',2),('B',1),('B',2)]
# islice — slice de generador infinito
list(itertools.islice(itertools.count(1), 5)) # => [1, 2, 3, 4, 5]
# groupby — agrupar elementos consecutivos
from itertools import groupby
datos = [("a", 1), ("a", 2), ("b", 3)]
for key, grupo in groupby(datos, key=lambda x: x[0]):
print(key, list(grupo))Bloque 5: Manejo de errores y context managers
21. ¿Cómo funciona el manejo de excepciones en Python?
def dividir(a, b):
try:
resultado = a / b
except ZeroDivisionError:
return None
except TypeError as e:
raise # re-lanza la excepción original
else:
return resultado # SOLO si no hubo excepciones
finally:
pass # se ejecuta SIEMPRE — limpiar recursos aquí
# Excepciones propias
class SaldoInsuficienteError(ValueError):
def __init__(self, saldo, monto):
super().__init__(f"Saldo {saldo} insuficiente para retirar {monto}")22. ¿Qué es un context manager y cómo se crea uno?
class ConexionDB:
def __enter__(self):
self.conexion = "conexion_simulada"
return self.conexion
def __exit__(self, tipo_exc, valor_exc, traceback):
print("Desconectando...")
return False # propaga la excepción
with ConexionDB() as conn:
print(f"Usando: {conn}")
# Con contextlib — más simple
from contextlib import contextmanager
@contextmanager
def tempdir():
import tempfile, shutil
d = tempfile.mkdtemp()
try:
yield d
finally:
shutil.rmtree(d) # siempre se limpiaBloque 6: Programación funcional
23. ¿Qué son `zip()`, `enumerate()` y `any()`/`all()`?
nombres = ["Ana", "Carlos", "María"]
edades = [25, 30, 28]
# zip — pares
agenda = dict(zip(nombres, edades))
# enumerate — índice + valor
for i, nombre in enumerate(nombres, start=1):
print(f"{i}. {nombre}")
# any / all
numeros = [1, 3, 5, 4, 7]
print(any(n % 2 == 0 for n in numeros)) # True
print(all(n > 0 for n in numeros)) # True24. ¿Qué es `functools.reduce()`?
from functools import reduce
producto = reduce(lambda acc, x: acc * x, [1, 2, 3, 4, 5])
# => 120
suma_con_inicial = reduce(lambda acc, x: acc + x, [1, 2, 3], 100)
# => 10625. ¿Qué es `functools.lru_cache`?
lru_cache memoiza resultados para evitar recálculos costosos.
from functools import lru_cache, cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
# Sin cache: O(2^n) | Con cache: O(n)
print(fibonacci(100)) # instantáneo
print(fibonacci.cache_info()) # hits, misses, currsize
@cache # Python 3.9+ — sin límite
def factorial(n):
return n * factorial(n - 1) if n else 1Bloque 7: Tipado estático y type hints
26. ¿Qué son los type hints y cómo se usan?
from typing import Optional, List, Dict, Tuple
def procesar(
nombres: List[str],
config: Optional[Dict[str, int]] = None,
) -> Tuple[int, List[str]]:
if config is None:
config = {}
filtrados = [n for n in nombres if len(n) > config.get("min_len", 0)]
return len(filtrados), filtrados
# Python 3.10+ — union type más limpio
def validar(valor: int | str | None) -> bool:
return valor is not None27. ¿Qué es un Protocol en Python?
Los Protocol permiten duck typing estructural — interfaces sin herencia explícita.
from typing import Protocol, runtime_checkable
@runtime_checkable
class Serializable(Protocol):
def to_dict(self) -> dict: ...
def to_json(self) -> str: ...
class Usuario:
def __init__(self, nombre: str):
self.nombre = nombre
def to_dict(self) -> dict:
return {"nombre": self.nombre}
def to_json(self) -> str:
import json
return json.dumps(self.to_dict())
# No hereda de Serializable, pero lo cumple
print(isinstance(Usuario("x"), Serializable)) # TrueBloque 8: Async/await y concurrencia
28. ¿Cuál es la diferencia entre threading, multiprocessing y asyncio?
- threading — I/O-bound, librerías síncronas. El GIL bloquea CPU-bound.
- multiprocessing — CPU-bound (cálculo pesado). Sin GIL compartido.
- asyncio — I/O-bound con alta concurrencia. No hay paralelismo real.
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.json()
async def main():
import aiohttp
urls = ["https://api.ejemplo.com/1", "https://api.ejemplo.com/2"]
async with aiohttp.ClientSession() as session:
resultados = await asyncio.gather(*[fetch(session, u) for u in urls])
return resultados
asyncio.run(main())29. ¿Qué es el GIL y cómo afecta al código Python?
El Global Interpreter Lock (GIL) permite que solo un thread ejecute bytecode CPython a la vez.
import multiprocessing
def calcular(n):
return sum(range(n))
# Multiprocessing — SÍ paraleliza (procesos separados, sin GIL compartido)
with multiprocessing.Pool(4) as pool:
resultados = pool.map(calcular, [10**7] * 4)
# concurrent.futures — API más simple
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor(max_workers=4) as ex:
resultados = list(ex.map(calcular, [10**7] * 4))30. ¿Cómo funcionan las coroutines y `async/await`?
import asyncio
async def tarea(nombre, segundos):
print(f"{nombre} iniciando")
await asyncio.sleep(segundos) # cede el control al event loop
print(f"{nombre} terminó")
return f"resultado de {nombre}"
async def main():
# Secuencial: 3s | Concurrente con gather: ~2s
r1, r2 = await asyncio.gather(
tarea("A", 1),
tarea("B", 2),
)
print(r1, r2)
asyncio.run(main())Bloque 9: Módulos y paquetes
31. ¿Cuál es la diferencia entre un módulo y un paquete?
myapp/
__init__.py # convierte el directorio en paquete
models/
__init__.py
usuario.py
utils.pyimport myapp.utils
from myapp.models.usuario import Usuario
# En __init__.py podés controlar qué se exporta:
# from .models.usuario import Usuario
# __all__ = ["Usuario"]32. ¿Qué hace `if __name__ == "__main__"`?
def sumar(a, b):
return a + b
# Solo se ejecuta cuando el archivo es el punto de entrada
# NO se ejecuta cuando se importa como módulo
if __name__ == "__main__":
print(sumar(3, 4)) # 7Bloque 10: Estructuras de datos y algoritmos
33. ¿Cómo invertís una lista sin usar `reverse()`?
lista = [1, 2, 3, 4, 5]
# Slicing — crea nueva lista
invertida = lista[::-1]
# In-place sin reverse() — O(n) tiempo, O(1) espacio
izq, der = 0, len(lista) - 1
while izq < der:
lista[izq], lista[der] = lista[der], lista[izq]
izq += 1
der -= 134. ¿Cómo encontrás el elemento más frecuente en una lista?
from collections import Counter
datos = [1, 3, 2, 1, 3, 1, 4, 3, 2, 1]
conteo = Counter(datos)
print(conteo.most_common(1)[0][0]) # 1
print(conteo.most_common(3)) # [(1, 4), (3, 3), (2, 2)]
# Sin Counter
def mas_frecuente(lista):
freq = {}
for x in lista:
freq[x] = freq.get(x, 0) + 1
return max(freq, key=freq.get)35. ¿Cómo implementás una pila y una cola en Python?
from collections import deque
# Pila (LIFO) — lista
pila = []
pila.append(1); pila.append(2)
tope = pila.pop() # O(1)
# Cola (FIFO) — deque, NO lista (list.pop(0) es O(n))
cola = deque()
cola.append(1); cola.append(2)
frente = cola.popleft() # O(1)
# Cola de prioridad
import heapq
heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 2)
print(heapq.heappop(heap)) # 1 — siempre el menor36. ¿Cómo implementás una búsqueda binaria?
def busqueda_binaria(arr, objetivo):
izq, der = 0, len(arr) - 1
while izq <= der:
mid = (izq + der) // 2
if arr[mid] == objetivo:
return mid
elif arr[mid] < objetivo:
izq = mid + 1
else:
der = mid - 1
return -1
arr = [1, 3, 5, 7, 9, 11, 13]
print(busqueda_binaria(arr, 7)) # => 3
# stdlib
import bisect
print(bisect.bisect_left(arr, 7)) # 337. ¿Cómo detectás si una cadena es palíndromo?
def es_palindromo(s: str) -> bool:
s = "".join(c.lower() for c in s if c.isalnum())
return s == s[::-1]
# Con dos punteros — O(1) espacio extra
def es_palindromo_v2(s: str) -> bool:
s = "".join(c.lower() for c in s if c.isalnum())
izq, der = 0, len(s) - 1
while izq < der:
if s[izq] != s[der]:
return False
izq += 1; der -= 1
return True
print(es_palindromo("A man a plan a canal Panama")) # True
print(es_palindromo("race a car")) # FalseBloque 11: Testing
38. ¿Cómo escribís tests con `pytest`?
import pytest
def sumar(a, b):
return a + b
def test_sumar():
assert sumar(2, 3) == 5
@pytest.mark.parametrize("a,b,esperado", [
(2, 3, 5), (-1, 1, 0), (100, -50, 50),
])
def test_sumar_param(a, b, esperado):
assert sumar(a, b) == esperado
def test_excepcion():
with pytest.raises(ValueError, match="División por cero"):
if 0 == 0: raise ValueError("División por cero")
@pytest.fixture
def usuario_base():
return {"nombre": "Ana", "saldo": 1000}
def test_usuario(usuario_base):
assert usuario_base["saldo"] == 100039. ¿Qué es mocking y cuándo se usa?
from unittest.mock import patch, MagicMock
def obtener_precio(pid: str) -> float:
import requests
return requests.get(f"https://api.precios.com/{pid}").json()["precio"]
def calcular_total(pid: str, cantidad: int) -> float:
return obtener_precio(pid) * cantidad
@patch("__main__.obtener_precio")
def test_calcular_total(mock_precio):
mock_precio.return_value = 10.0
assert calcular_total("ABC", 3) == 30.0
mock_precio.assert_called_once_with("ABC")Bloque 12: Patrones avanzados
40. ¿Qué es el patrón Singleton en Python?
class SingletonMeta(type):
_instancias = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instancias:
cls._instancias[cls] = super().__call__(*args, **kwargs)
return cls._instancias[cls]
class Config(metaclass=SingletonMeta):
def __init__(self):
self.debug = False
c1 = Config()
c2 = Config()
print(c1 is c2) # True41. ¿Cómo implementás el patrón Observer?
from typing import Callable, List
class EventEmitter:
def __init__(self):
self._listeners: dict[str, List[Callable]] = {}
def on(self, evento: str, callback: Callable):
self._listeners.setdefault(evento, []).append(callback)
return self
def emit(self, evento: str, *args, **kwargs):
for cb in self._listeners.get(evento, []):
cb(*args, **kwargs)
emitter = EventEmitter()
emitter.on("usuario.creado", lambda u: print(f"Bienvenido {u}"))
emitter.on("usuario.creado", lambda u: print(f"Email enviado a {u}"))
emitter.emit("usuario.creado", "Ana")42. ¿Qué es una metaclass en Python?
class ValidadorAtributos(type):
def __new__(mcs, nombre, bases, namespace):
for attr in namespace:
if not attr.startswith("_") and not attr.isupper():
raise ValueError(f"{attr!r} debe estar en MAYÚSCULAS")
return super().__new__(mcs, nombre, bases, namespace)
# Uso más común: ABCs
from abc import ABC, abstractmethod
class Figura(ABC):
@abstractmethod
def area(self) -> float: ...
@abstractmethod
def perimetro(self) -> float: ...43. ¿Qué es un descriptor en Python?
class Tipado:
def __init__(self, tipo):
self.tipo = tipo; self.nombre = None
def __set_name__(self, owner, nombre):
self.nombre = nombre
def __get__(self, obj, objtype=None):
if obj is None: return self
return obj.__dict__.get(self.nombre)
def __set__(self, obj, valor):
if not isinstance(valor, self.tipo):
raise TypeError(f"{self.nombre} debe ser {self.tipo.__name__}")
obj.__dict__[self.nombre] = valor
class Persona:
nombre = Tipado(str)
edad = Tipado(int)
def __init__(self, nombre, edad):
self.nombre = nombre
self.edad = edadBloque 13: Rendimiento y optimización
44. ¿Cómo medís y optimizás el rendimiento de Python?
import timeit
# join es ~10x más rápido que concatenación con +
timeit.timeit('"".join(palabras)', setup='palabras=["hola"]*1000', number=1000)
import cProfile
cProfile.run("funcion_lenta()")
# __slots__ reduce memoria en clases con muchas instancias
class PuntoOptimizado:
__slots__ = ("x", "y") # evita __dict__ por instancia
def __init__(self, x, y):
self.x = x; self.y = y45. ¿Cuándo usás `numpy` sobre listas?
import numpy as np
arr = np.arange(1_000_000)
# Suma de cuadrados: Python puro ~200ms | NumPy ~2ms
resultado = np.sum(arr ** 2)
# Broadcasting — sin loops
a = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3)
b = np.array([10, 20, 30]) # shape (3,)
c = a + b # broadcast: (2, 3)
# Indexing booleano
datos = np.array([1, -2, 3, -4, 5])
positivos = datos[datos > 0] # => [1, 3, 5]Bloque 14: Pythonismos y best practices
46. ¿Cuáles son los principales principios de código pythónico?
# Intercambio sin variable temporal
a, b = b, a
# Unpacking extendido
primero, *resto = [1, 2, 3, 4, 5]
*inicio, ultimo = [1, 2, 3, 4, 5]
primero, *m, ult = [1, 2, 3, 4, 5]
# Walrus operator (Python 3.8+)
import re
if m := re.search(r"\d+", "hay 42 errores"):
print(m.group()) # "42"
# EAFP — más pythónico
try:
valor = diccionario["key"]
except KeyError:
valor = default
# Concatenación eficiente
resultado = " ".join(["Hola", "mundo", "Python"])47. ¿Qué es PEP 8 y cuáles son sus reglas más importantes?
mi_variable = 1 # snake_case para variables/funciones
MiClase = True # PascalCase para clases
MI_CONSTANTE = 42 # UPPER_SNAKE para constantes
_privado = True # _ para atributos privados (convención)
def funcion(a, b=0): # sin espacio en default args
pass
# Líneas largas con paréntesis
resultado = (
muy_largo_nombre
+ otro_nombre
+ un_tercero
)48. ¿Cómo usás `collections.defaultdict` y `collections.namedtuple`?
from collections import defaultdict, namedtuple
# defaultdict — evita KeyError
conteo = defaultdict(int)
for palabra in "el gato come el pescado".split():
conteo[palabra] += 1
agrupado = defaultdict(list)
for k, v in [("A", 1), ("B", 2), ("A", 3)]:
agrupado[k].append(v)
# => {"A": [1, 3], "B": [2]}
# namedtuple — tupla con nombres
Punto = namedtuple("Punto", ["x", "y"])
p = Punto(3, 4)
print(p.x, p.y) # acceso por nombre
x, y = p # unpacking también funciona49. ¿Qué son las variables de entorno y cómo se manejan en Python?
import os
from pathlib import Path
db_url = os.environ.get("DATABASE_URL", "sqlite:///local.db")
debug = os.environ.get("DEBUG", "false").lower() == "true"
# pathlib — manejo moderno de rutas
ruta = Path("/home/usuario/proyecto")
archivo = ruta / "datos" / "config.json"
if archivo.exists():
contenido = archivo.read_text(encoding="utf-8")
for py_file in ruta.glob("**/*.py"):
print(py_file)
(ruta / "output").mkdir(parents=True, exist_ok=True)50. ¿Cómo estructurás un proyecto Python profesional?
La estructura recomendada usa src layout con pyproject.toml:
mi_proyecto/
├── src/
│ └── mi_paquete/
│ ├── __init__.py
│ ├── models.py
│ ├── services.py
│ └── utils.py
├── tests/
│ ├── test_models.py
│ └── test_services.py
├── pyproject.toml
├── .env.example
└── README.md[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mi-paquete"
version = "0.1.0"
dependencies = ["requests>=2.31", "pydantic>=2.0"]
[tool.pytest.ini_options]
testpaths = ["tests"]
[tool.mypy]
strict = trueCómo usar esta guía para preparar tu entrevista
Semana antes: repasá los bloques de fundamentos (1-5), funciones (6-10) y OOP (11-15).
Día anterior: bloques de generadores, async/await y algoritmos (16-37).
La mañana de la entrevista: repasá los pythonismos (46-50) — son los que los entrevistadores esperan que domines naturalmente.
En la entrevista: cuando recibas un ejercicio, pensá en voz alta. Los entrevistadores evalúan tu proceso tanto como la solución final. La práctica más efectiva es escribir el código de memoria, no leerlo — cerrá esta página y reescribí las funciones que te resultaron menos claras.