Guía de Python para Data Engineering
Python es un lenguaje de programación de alto nivel, interpretado y de propósito general. Se volvió el lenguaje dominante en Data, Machine Learning e Inteligencia Artificial gracias a su sintaxis simple, su enorme biblioteca estándar y un ecosistema de librerías (Pandas, NumPy, PySpark, scikit-learn, entre muchas otras) que cubre todo el pipeline de datos: desde la ingesta hasta el despliegue de modelos.
Esta guía resume lo esencial del lenguaje con foco práctico: cómo pensar las variables y estructuras de datos, cómo estructurar funciones y clases, cómo manejar errores y archivos, y qué buenas prácticas conviene adoptar desde el día uno si vas a trabajar con Python en proyectos reales de datos.
Por qué Python domina en datos e IA
No es casualidad que Python se haya convertido en el estándar de facto en la industria de datos:
- Sintaxis clara y legible, con una curva de aprendizaje suave tanto para principiantes como para expertos.
- Comunidad enorme y activa, que sostiene documentación, librerías y soporte constante.
- Ecosistema riquísimo: NumPy, Pandas y Polars para análisis; Matplotlib, Seaborn y Plotly para visualización; scikit-learn, TensorFlow y PyTorch para machine learning; PySpark, DuckDB y PyIceberg para trabajar a gran escala.
- Versatilidad: permite cubrir todo el pipeline, desde la recolección de datos hasta el despliegue de modelos en producción.
- Multiplataforma, corriendo igual en Windows, macOS y Linux.
Como contrapartida, Python es más lento que lenguajes compilados como C++ en operaciones intensivas, consume más memoria por su tipado dinámico y garbage collector, y el GIL (Global Interpreter Lock) limita el paralelismo nativo con hilos. En la práctica, estas limitaciones se resuelven delegando el cómputo pesado a librerías escritas en C/C++ (como NumPy) o a motores distribuidos (como Spark), mientras Python actúa como capa de orquestación.
Fundamentos del lenguaje
Variables y tipos de datos
Una variable es un contenedor que almacena un dato y puede modificarse durante la ejecución. En Python no hace falta declarar el tipo explícitamente: se infiere en tiempo de ejecución (tipado dinámico).
mi_variable = 10
nombre = "Alice"
# Asignación múltiple
a, b = 5, 6
# Eliminar una variable
del nombre
Los tipos primitivos básicos son enteros (int), flotantes (float), cadenas de texto (str) y booleanos (bool). Las estructuras compuestas más usadas son:
mi_lista = [1, 2, 3] # lista: mutable, ordenada
mi_tupla = (1, 2, 3) # tupla: inmutable, ordenada
mi_diccionario = {"clave": "valor", "numero": 10} # pares clave-valor
mi_conjunto = {1, 2, 3} # set: sin duplicados, sin orden garantizado
Para convertir entre tipos se usa casting:
x = int(2.8) # 2
y = float("3") # 3.0
z = str(2) # "2"
Tip: Usá
snake_casepara nombres de variables y funciones, nombres descriptivos (precio_totalen vez deprec_tot) y evitá abreviaturas ambiguas. Es una convención de la comunidad (PEP8) que mejora mucho la legibilidad en proyectos colaborativos.
Operadores
Python soporta operadores aritméticos, de asignación, de comparación, lógicos, de identidad, de pertenencia y a nivel de bits.
# Aritméticos
print(5 + 3, 5 - 3, 5 * 3, 5 / 3, 5 % 3, 5 // 3, 5 ** 3)
# Comparación
print(5 == 3, 5 != 3, 5 > 3, 5 <= 3)
# Lógicos
print(True and False, True or False, not True)
# Identidad y pertenencia
a = [1, 2, 3]
b = a
print(a is b) # True: mismo objeto en memoria
print(2 in a) # True: pertenencia
La diferencia entre == e is es clave: == compara valores, is compara identidad (si son el mismo objeto en memoria).
Strings, listas, diccionarios y sets
Los strings se manipulan con métodos como .upper(), .lower(), .strip(), y se formatean con f-strings, la forma moderna y más legible de interpolar valores:
nombre = "Mundo"
mensaje = f"Hola {nombre}"
edad = 25
print(f"Tienes {edad} años")
print(f"{nombre=}") # Python 3.8+: imprime "nombre='Mundo'", útil para debug
Las listas son la estructura de trabajo por defecto: ordenadas, mutables y con métodos como .append(), .sort(), slicing (lista[1:3]), comprensiones, etc. Las tuplas son su versión inmutable, ideales para datos que no deben cambiar (coordenadas, registros fijos). Los diccionarios son la estructura clave para representar registros y JSON:
diccionario = {"clave": "valor", "numero": 10}
valor = diccionario["clave"]
for clave, valor in diccionario.items():
print(f"Clave: {clave}, Valor: {valor}")
Los sets sirven para deduplicar y hacer operaciones de conjuntos (unión, intersección) de forma eficiente.
Estructuras de control
if/elif/else permiten tomar decisiones. En las condiciones se combinan operadores de comparación (==, !=, <, >), lógicos (and, or, not), de pertenencia (in, not in) y de identidad (is, is not):
puntuacion = 85
if puntuacion >= 90:
print("Tienes una A")
elif puntuacion >= 80:
print("Tienes una B")
else:
print("Sigue practicando")
El operador ternario condensa un if/else simple en una línea:
edad = 20
es_adulto = "Sí" if edad >= 18 else "No"
Para iterar, for es el bucle definido (se sabe de antemano cuántas veces itera) y while es el bucle indefinido (depende de una condición dinámica):
for nombre in ["Ana", "Bruno", "Carlos"]:
print(nombre)
for i in range(10): # itera de 0 a 9
print(i)
contador = 0
while contador < 5:
print(contador)
contador += 1 # imprescindible para evitar un bucle infinito
Desde Python 3.10 existe también match, un equivalente más expresivo al switch de otros lenguajes, útil para comparar una variable contra varios patrones:
match evento.tipo:
case "click":
print("El usuario hizo click")
case "scroll":
print("El usuario hizo scroll")
case _:
print("Evento desconocido")
Y el walrus operator (:=), que permite asignar y evaluar en la misma expresión, típico en bucles while:
while (linea := input("Escribe algo: ")) != "salir":
print(f"Entrada: {linea}")
Funciones
Una función encapsula un bloque de código reutilizable. Se define con def:
def calcular_promedio(lista_numeros):
"""Calcula el promedio de una lista de números."""
return sum(lista_numeros) / len(lista_numeros)
Los parámetros pueden tener valores por defecto (haciéndolos opcionales), y se pueden pasar por posición o por nombre (keyword arguments):
def imprimir_mensaje(mensaje, repetir=1):
for _ in range(repetir):
print(mensaje)
imprimir_mensaje("Hola", 3) # posicional
imprimir_mensaje(mensaje="Hola", repetir=3) # nombrado
*args y **kwargs permiten aceptar un número variable de argumentos posicionales y nombrados respectivamente:
def sumar_numeros(*args):
return sum(args)
def configurar_perfil(**kwargs):
for clave, valor in kwargs.items():
print(f"{clave}: {valor}")
sumar_numeros(10, 20, 30)
configurar_perfil(nombre="Lucas", edad=30, ciudad="Buenos Aires")
Una función puede devolver múltiples valores, que en realidad se empaquetan como tupla:
def operaciones_basicas(a, b):
return a + b, a - b # retorna una tupla
suma, resta = operaciones_basicas(2, 3)
Las funciones lambda son funciones anónimas de una sola expresión, muy usadas junto a map() y filter():
cuadrados = list(map(lambda x: x**2, [1, 2, 3, 4, 5]))
pares = list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5]))
Tip: Preferí funciones puras (sin efectos secundarios, mismo input → mismo output) siempre que puedas. Son más fáciles de testear, razonar y reutilizar que las funciones que dependen de o modifican estado global.
Respecto al alcance de variables: las definidas dentro de una función son locales y desaparecen al terminar la ejecución; las definidas fuera son globales y accesibles desde cualquier lado (aunque para modificarlas dentro de una función hace falta la palabra clave global). En funciones anidadas, nonlocal permite modificar una variable del alcance intermedio (ni local ni global).
Programación orientada a objetos
La POO organiza el código en torno a clases (plantillas) y objetos (instancias con atributos y métodos):
class Persona:
def __init__(self, nombre, edad):
self.nombre = nombre
self.edad = edad
def saludar(self):
return f"Hola, mi nombre es {self.nombre} y tengo {self.edad} años."
persona1 = Persona("Juan", 30)
print(persona1.saludar())
Los cuatro pilares de la POO en Python:
Encapsulamiento: proteger datos internos exponiendo solo una interfaz controlada, usando atributos "privados" (prefijo _ o __) y properties para acceso controlado:
class CuentaBancaria:
def __init__(self, titular, saldo):
self.titular = titular
self.__saldo = saldo # atributo privado
@property
def saldo(self):
return self.__saldo
def depositar(self, cantidad):
if cantidad > 0:
self.__saldo += cantidad
Herencia: crear clases nuevas a partir de clases existentes, reutilizando y extendiendo comportamiento con super():
class Animal:
def __init__(self, nombre):
self.nombre = nombre
def hacer_sonido(self):
raise NotImplementedError
class Perro(Animal):
def hacer_sonido(self):
return "Guau"
Polimorfismo: tratar objetos de distintas clases de forma uniforme, siempre que respondan a la misma interfaz (duck typing):
def animal_sonido(animal):
return animal.hacer_sonido()
# Funciona igual para cualquier clase que implemente hacer_sonido()
Abstracción: exponer solo lo esencial, ocultando la complejidad de implementación. El módulo abc permite definir clases e interfaces abstractas que fuerzan a las subclases a implementar ciertos métodos.
Tip: Usá POO cuando el problema modela entidades del mundo real con estado y comportamiento propio (sistemas grandes, pipelines con múltiples componentes). Para scripts de procesamiento de datos simples, funciones y programación más directa suelen ser más rápidas de escribir y mantener.
Manejo de errores
Las excepciones permiten manejar errores en tiempo de ejecución sin que el programa se caiga abruptamente. La estructura básica es try/except/else/finally:
try:
x = 1 / 0
except ZeroDivisionError:
print("No se puede dividir por cero.")
else:
print("División exitosa.") # se ejecuta si NO hubo excepción
finally:
print("Ejecución finalizada.") # se ejecuta siempre
Las excepciones más comunes en Python: TypeError, ValueError, KeyError, IndexError, AttributeError, FileNotFoundError, ImportError, ZeroDivisionError. Vale la pena capturar excepciones específicas en vez de usar un except Exception genérico, para no ocultar errores inesperados.
Se pueden forzar excepciones con raise, y crear excepciones propias heredando de Exception:
class ErrorDeValidacion(Exception):
"""Excepción personalizada para errores de validación."""
pass
def validar_edad(edad):
if not isinstance(edad, int):
raise ErrorDeValidacion("La edad debe ser un número entero.")
return True
Los context managers (with) garantizan que los recursos se liberen correctamente incluso si ocurre una excepción, y son el patrón estándar para trabajar con archivos y conexiones:
with open('archivo.txt', 'r') as archivo:
contenido = archivo.read()
Tip: No uses excepciones para controlar el flujo normal del programa. Son para situaciones excepcionales, no para reemplazar un
if.
Manejo de archivos
open() es la función central para trabajar con archivos, con modos 'r' (lectura), 'w' (escritura, sobrescribe), 'a' (append) y sus variantes binarias ('rb', 'wb'). Siempre conviene usar with para que el archivo se cierre automáticamente:
with open('mi_archivo.txt', 'r') as archivo:
contenido = archivo.read()
with open('mi_archivo.txt', 'a') as archivo:
archivo.write('Una línea nueva.\n')
Para leer línea por línea, readline() (una a la vez) o readlines() (todas en una lista).
Para CSV y JSON, formatos omnipresentes en pipelines de datos:
import csv
import json
# CSV
with open('datos.csv', 'r') as f:
lector = csv.reader(f)
for fila in lector:
print(fila)
# JSON
with open('datos.json', 'r') as f:
datos = json.load(f)
with open('datos.json', 'w') as f:
json.dump(datos, f, indent=4)
Para datasets reales, Pandas simplifica enormemente la lectura y escritura de CSV y Excel:
import pandas as pd
df = pd.read_csv('ventas.csv')
print(df.describe())
ventas_por_producto = df.groupby('producto')['ventas'].sum()
Entornos virtuales
Los entornos virtuales aíslan las dependencias de cada proyecto para evitar conflictos de versiones entre librerías. Es una práctica que conviene aplicar siempre, incluso en proyectos chicos.
| Herramienta | Gestión de versiones de Python | Entornos virtuales | Dependencias | Cuándo usarla |
|---|---|---|---|---|
venv | No | Sí | Limitada (con pip) | Proyectos simples, viene incluido en Python |
pipenv | No | Sí | Sí | Gestión todo-en-uno con Pipfile |
pyenv | Sí | Opcional | No | Cambiar versiones de Python en el sistema |
poetry | No | Sí | Sí | Proyectos nuevos, empaquetado y dependencias complejas |
conda | Sí | Sí | Sí | Data Science / ML, incluso con dependencias no-Python |
Crear y activar un entorno con venv:
python -m venv .venv
# Activar (Unix/macOS)
source .venv/bin/activate
# Activar (Windows)
.venv\Scripts\activate
# Desactivar
deactivate
Tip: Documentá siempre las dependencias con
pip freeze > requirements.txty agregá.venv/a tu.gitignore. Nunca instales paquetes en el entorno global de Python.
Logging
print() sirve para depurar rápido, pero en aplicaciones reales el módulo logging da mucho más control: niveles de severidad, formato configurable, persistencia en archivos y compatibilidad con entornos concurrentes.
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
)
logger = logging.getLogger(__name__)
def dividir(a, b):
if b == 0:
logger.error("Intento de división por cero.")
raise ZeroDivisionError("El denominador no puede ser cero.")
return a / b
Los cinco niveles estándar, de menor a mayor severidad: DEBUG, INFO, WARNING, ERROR, CRITICAL. Usá siempre logging.getLogger(__name__) para mantener la jerarquía de módulos, y logger.exception() dentro de un except para registrar el traceback completo automáticamente.
Testing con pytest
Las pruebas unitarias verifican que cada unidad de código funcione de forma aislada. unittest viene incluido en Python, pero pytest es hoy el estándar de facto por su sintaxis simple y su ecosistema de plugins.
# test_calculadora.py
def multiplicar(a, b):
return a * b
def test_multiplicar():
assert multiplicar(4, 2) == 8
assert multiplicar(0, 5) == 0
assert multiplicar(-2, 3) == -6
pytest test_calculadora.py
Buenas prácticas: nombrar los tests con el prefijo test_, mantenerlos independientes entre sí, cubrir casos de borde y separar el código fuente (src/) de las pruebas (tests/).
Buenas prácticas para convertirte en experto
- Usá entornos virtuales en todos tus proyectos, sin excepción.
- Seguí PEP8 para mantener el código limpio y consistente. Herramientas como Black (formateo automático) y Pylint o Ruff (linting) automatizan esto.
- Pensá en soluciones modulares: dividí el código en funciones, clases y módulos con una responsabilidad clara cada uno.
- Documentá con docstrings el propósito, parámetros y retorno de tus funciones.
- Escribí pruebas para el código crítico, no solo al final del proyecto.
- Usá logging en vez de print() para monitorear aplicaciones en producción.
- Versioná tu código con Git desde el primer commit.
Tip: El error más común de quien está aprendiendo Python es acumular tutoriales sin construir nada propio. La forma más rápida de progresar es tomar un problema real —por chico que sea— y resolverlo de punta a punta, incluyendo manejo de errores, tests y documentación mínima.
Python para Data Engineering e IA
Más allá del lenguaje en sí, el motivo por el que Python es hoy la herramienta central en datos e IA es su ecosistema de librerías, organizado por área:
| Área | Librerías | Uso |
|---|---|---|
| Análisis de datos | NumPy, Pandas, Polars, SciPy | Procesamiento numérico y manipulación de datos |
| Visualización | Matplotlib, Seaborn, Plotly | Gráficos estáticos e interactivos |
| Machine Learning | scikit-learn, TensorFlow, PyTorch, XGBoost | Modelos predictivos y deep learning |
| IA generativa | OpenAI, LangChain | LLMs y generación de contenido |
| Data Engineering | PySpark, Polars, DuckDB, PyIceberg | Procesamiento distribuido y formatos de datos |
| APIs y backend | FastAPI, Django, Flask | Exposición de servicios y modelos |
Este ecosistema es lo que sostiene roles como Data Scientist, Data Engineer, ML Engineer o MLOps Engineer, todos con Python como lenguaje base. Jupyter Notebooks y Google Colab son los entornos más usados para exploración y prototipado, mientras que en producción el código migra a scripts, paquetes y servicios versionados como cualquier otro software.
Dominar los fundamentos que cubre esta guía —variables, estructuras de datos, funciones, POO, manejo de errores, archivos y buenas prácticas— es la base sólida sobre la que se apoya después todo ese ecosistema especializado.
