Fundamentos de Python
Notebook interactivo: este tema tiene un notebook con ejemplos ejecutables y explicaciones más profundas sobre memoria, el intérprete, y patrones eficientes.
Ahora sí, vamos a escribir código. Esta sección cubre lo esencial: tipos de datos, variables, operadores, condicionales y ciclos. Si ya programaste en otro lenguaje, esto va a ser rápido — la sintaxis de Python es deliberadamente minimalista.
Puedes probar todo en el REPL (
python3) o en un script (.py). Si es algo corto, el REPL es más rápido. Si son más de 5 líneas, usa un script.
Variables y asignación
En Python no declaras tipos. Simplemente asignas un valor a un nombre:
x = 42
nombre = "Ana"
pi = 3.14159
activo = True
Python infiere el tipo por el valor. No necesitas escribir int x = 42 como en Java o C.
Reglas para nombres de variables
- Letras, números y guiones bajos:
mi_variable,dato_2,_privado - No pueden empezar con número:
2datoes inválido - No pueden ser palabras reservadas:
if,for,class,return, etc. - Python es case-sensitive:
nombreyNombreson variables diferentes
Convención: snake_case
En Python la convención es snake_case para variables y funciones:
# ✅ Correcto (snake_case)
mi_variable = 10
nombre_completo = "Ana García"
# ❌ Incorrecto (camelCase — eso es JavaScript)
miVariable = 10
nombreCompleto = "Ana García"
Tipos de datos básicos
Python tiene cuatro tipos escalares (un solo valor) y dos tipos colección (múltiples valores) que vas a usar todo el tiempo.
int — números enteros
>>> edad = 25
>>> poblacion = 130_000_000 # los guiones bajos son separadores visuales
>>> negativo = -42
>>> type(edad)
<class 'int'>
Los enteros en Python no tienen límite de tamaño. Puedes calcular 2 ** 1000 sin problemas.
float — números decimales
>>> pi = 3.14159
>>> temperatura = -5.2
>>> cientifico = 6.022e23 # notación científica: 6.022 × 10²³
>>> type(pi)
<class 'float'>
Cuidado con floats:
0.1 + 0.2da0.30000000000000004, no0.3. Esto no es un bug de Python — es cómo funciona la aritmética de punto flotante en todas las computadoras. Para dinero o precisión exacta, usa el módulodecimal.
str — cadenas de texto
>>> saludo = "Hola, mundo"
>>> otro = 'También funciona con comillas simples'
>>> multilinea = """Este string
... tiene varias
... líneas"""
>>> type(saludo)
<class 'str'>
Los strings son inmutables — no puedes cambiar un carácter individual. Si necesitas modificar un string, creas uno nuevo.
Operaciones con strings
>>> "Hola" + " " + "mundo" # concatenación
'Hola mundo'
>>> "ja" * 3 # repetición
'jajaja'
>>> len("Python") # longitud
6
>>> "python".upper() # mayúsculas
'PYTHON'
>>> " hola ".strip() # quitar espacios
'hola'
>>> "hola mundo".split() # dividir en lista
['hola', 'mundo']
>>> "a,b,c".split(",") # dividir por coma
['a', 'b', 'c']
f-strings: interpolación de variables
La forma moderna de incluir variables en strings:
nombre = "Ana"
edad = 25
print(f"Me llamo {nombre} y tengo {edad} años")
# Me llamo Ana y tengo 25 años
# Puedes poner expresiones dentro de las llaves
print(f"En 10 años tendré {edad + 10} años")
# En 10 años tendré 35 años
La f antes de las comillas activa la interpolación. Todo lo que esté entre {} se evalúa como código Python.
bool — verdadero o falso
>>> activo = True
>>> eliminado = False
>>> type(activo)
<class 'bool'>
Solo dos valores posibles: True y False (con mayúscula inicial).
Valores “truthy” y “falsy”
En Python, muchos valores se evalúan como True o False en contextos booleanos:
| Falsy (se evalúa como False) | Truthy (se evalúa como True) |
|---|---|
False |
True |
0, 0.0 |
Cualquier otro número |
"" (string vacío) |
Cualquier string no vacío |
[] (lista vacía) |
Lista con elementos |
{} (dict vacío) |
Dict con elementos |
None |
Casi todo lo demás |
Esto es útil para verificaciones rápidas:
nombre = ""
if nombre:
print(f"Hola, {nombre}")
else:
print("No se proporcionó nombre")
# No se proporcionó nombre
list — listas ordenadas
Una lista es una colección ordenada y mutable de elementos:
>>> numeros = [1, 2, 3, 4, 5]
>>> nombres = ["Ana", "Luis", "María"]
>>> mezcla = [1, "dos", 3.0, True] # puede mezclar tipos
>>> vacia = []
>>> type(numeros)
<class 'list'>
Operaciones con listas
# Acceso por índice (empieza en 0)
>>> numeros[0]
1
>>> numeros[-1] # último elemento
5
# Slicing
>>> numeros[1:3] # del índice 1 al 2 (no incluye el 3)
[2, 3]
>>> numeros[:3] # primeros 3
[1, 2, 3]
>>> numeros[2:] # del 2 en adelante
[3, 4, 5]
# Modificar
>>> numeros[0] = 99
>>> numeros
[99, 2, 3, 4, 5]
# Agregar
>>> numeros.append(6) # al final
>>> numeros.insert(0, 0) # en posición específica
# Eliminar
>>> numeros.remove(99) # por valor
>>> numeros.pop() # último elemento
>>> del numeros[0] # por índice
# Longitud
>>> len(numeros)
4
# Ordenar
>>> [3, 1, 2].sort() # in-place (modifica la lista)
>>> sorted([3, 1, 2]) # retorna nueva lista ordenada
[1, 2, 3]
# Verificar si un elemento existe
>>> 3 in [1, 2, 3]
True
dict — diccionarios (clave-valor)
Un diccionario mapea claves a valores. Es la estructura más útil de Python:
>>> persona = {
... "nombre": "Ana",
... "edad": 25,
... "ciudad": "CDMX"
... }
>>> type(persona)
<class 'dict'>
Operaciones con diccionarios
# Acceso por clave
>>> persona["nombre"]
'Ana'
# Acceso seguro (retorna None si no existe)
>>> persona.get("telefono")
None
>>> persona.get("telefono", "no disponible")
'no disponible'
# Agregar o modificar
>>> persona["email"] = "ana@mail.com"
>>> persona["edad"] = 26
# Eliminar
>>> del persona["ciudad"]
# Verificar si una clave existe
>>> "nombre" in persona
True
# Obtener claves y valores
>>> persona.keys()
dict_keys(['nombre', 'edad', 'email'])
>>> persona.values()
dict_values(['Ana', 26, 'ana@mail.com'])
>>> persona.items()
dict_items([('nombre', 'Ana'), ('edad', 26), ('email', 'ana@mail.com')])
# Longitud
>>> len(persona)
3
Tabla resumen de tipos
| Tipo | Ejemplo | Mutable | Uso típico |
|---|---|---|---|
int |
42 |
— | Conteos, índices |
float |
3.14 |
— | Cálculos numéricos |
str |
"hola" |
No | Texto, nombres, IDs |
bool |
True |
— | Condiciones, flags |
list |
[1, 2, 3] |
Sí | Colecciones ordenadas |
dict |
{"a": 1} |
Sí | Mapeos clave-valor, registros |
Operadores
Aritméticos
>>> 7 + 3 # suma: 10
>>> 7 - 3 # resta: 4
>>> 7 * 3 # multiplicación: 21
>>> 7 / 3 # división (siempre float): 2.3333...
>>> 7 // 3 # división entera: 2
>>> 7 % 3 # módulo (residuo): 1
>>> 7 ** 3 # potencia: 343
Comparación
>>> 5 == 5 # igual: True
>>> 5 != 3 # diferente: True
>>> 5 > 3 # mayor que: True
>>> 5 < 3 # menor que: False
>>> 5 >= 5 # mayor o igual: True
>>> 5 <= 3 # menor o igual: False
Lógicos
>>> True and False # AND: False
>>> True or False # OR: True
>>> not True # NOT: False
Combinaciones comunes:
edad = 25
if edad >= 18 and edad < 65:
print("Edad laboral")
Condicionales: if, elif, else
Python usa indentación (espacios) para definir bloques de código. No hay llaves {} ni end — la indentación es la estructura.
edad = 20
if edad < 18:
print("Menor de edad")
elif edad < 65:
print("Adulto")
else:
print("Adulto mayor")
IMPORTANTE: la indentación estándar en Python es 4 espacios. No uses tabs. Configura tu editor para que Tab inserte 4 espacios.
Reglas
- La condición va después de
if/elif, seguida de: - El bloque indentado se ejecuta si la condición es
True elifes la abreviación de “else if” — puedes tener cuantos quieraselsees opcional — se ejecuta si ninguna condición fueTrue- Python evalúa de arriba a abajo y ejecuta el primer bloque cuya condición sea
True
Ejemplos
# Simple
x = 10
if x > 0:
print("Positivo")
# Con else
temperatura = -5
if temperatura > 0:
print("Sobre cero")
else:
print("Bajo cero")
# Con elif
nota = 85
if nota >= 90:
print("A")
elif nota >= 80:
print("B")
elif nota >= 70:
print("C")
else:
print("F")
# B
# Condiciones compuestas
edad = 25
tiene_id = True
if edad >= 18 and tiene_id:
print("Puede entrar")
Indentación incorrecta = error
# ❌ Esto da IndentationError
if True:
print("mal")
# ❌ Esto también (mezcla de espacios e indentación)
if True:
print("línea 1")
print("línea 2") # indentación inconsistente
# ✅ Correcto
if True:
print("línea 1")
print("línea 2")
Ciclos: for y while
for — iterar sobre una secuencia
for recorre cada elemento de una secuencia (lista, string, rango, etc.):
# Recorrer una lista
frutas = ["manzana", "naranja", "plátano"]
for fruta in frutas:
print(fruta)
# manzana
# naranja
# plátano
# Recorrer un string
for letra in "Python":
print(letra)
# P, y, t, h, o, n (cada una en su línea)
# range(): generar secuencias de números
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 8): # 2, 3, 4, 5, 6, 7
print(i)
for i in range(0, 10, 2): # 0, 2, 4, 6, 8 (de 2 en 2)
print(i)
Patrones comunes con for
# Sumar elementos
numeros = [10, 20, 30, 40]
total = 0
for n in numeros:
total += n
print(total) # 100
# Filtrar elementos
edades = [15, 22, 17, 30, 12]
adultos = []
for edad in edades:
if edad >= 18:
adultos.append(edad)
print(adultos) # [22, 30]
# Recorrer un diccionario
persona = {"nombre": "Ana", "edad": 25, "ciudad": "CDMX"}
for clave, valor in persona.items():
print(f"{clave}: {valor}")
# nombre: Ana
# edad: 25
# ciudad: CDMX
# enumerate: obtener índice y valor
frutas = ["manzana", "naranja", "plátano"]
for i, fruta in enumerate(frutas):
print(f"{i}: {fruta}")
# 0: manzana
# 1: naranja
# 2: plátano
while — repetir mientras una condición sea verdadera
contador = 0
while contador < 5:
print(contador)
contador += 1
# 0, 1, 2, 3, 4
Cuidado con ciclos infinitos: si la condición nunca se vuelve
False, el ciclo corre para siempre. UsaCtrl+Cpara interrumpirlo.
# ❌ Ciclo infinito (falta incrementar el contador)
x = 0
while x < 10:
print(x)
# x nunca cambia → imprime 0 para siempre
break y continue
# break: salir del ciclo inmediatamente
for i in range(100):
if i == 5:
break
print(i)
# 0, 1, 2, 3, 4
# continue: saltar a la siguiente iteración
for i in range(10):
if i % 2 == 0:
continue # salta los pares
print(i)
# 1, 3, 5, 7, 9
¿for o while?
Usa for cuando… |
Usa while cuando… |
|---|---|
| Sabes cuántas iteraciones hay | No sabes cuántas iteraciones |
| Recorres una colección | Esperas una condición |
Usas range() |
Lees input del usuario |
| 90% de los casos | Casos especiales |
En ciencia de datos, for se usa mucho más que while.
Funciones
Una función es un bloque de código reutilizable. Se define con def:
def saludar(nombre):
print(f"Hola, {nombre}!")
saludar("Ana") # Hola, Ana!
saludar("Luis") # Hola, Luis!
Retornar valores
def sumar(a, b):
return a + b
resultado = sumar(3, 4)
print(resultado) # 7
Sin return, la función retorna None implícitamente.
Valores por defecto
def saludar(nombre, saludo="Hola"):
print(f"{saludo}, {nombre}!")
saludar("Ana") # Hola, Ana!
saludar("Ana", "Buenos días") # Buenos días, Ana!
Múltiples valores de retorno
Python puede retornar varios valores a la vez usando tuplas:
def min_max(numeros):
return min(numeros), max(numeros)
minimo, maximo = min_max([3, 1, 4, 1, 5, 9])
print(f"Mín: {minimo}, Máx: {maximo}")
# Mín: 1, Máx: 9
None
None es el valor “vacío” de Python. Es el equivalente a null en otros lenguajes:
resultado = None
if resultado is None:
print("No hay resultado")
Usa
is None/is not None, no== None. Es la convención de Python y es técnicamente más correcto.
Conversión de tipos
Python no convierte tipos automáticamente en la mayoría de los casos. Necesitas hacerlo explícitamente:
# String a número
>>> int("42")
42
>>> float("3.14")
3.14
# Número a string
>>> str(42)
'42'
# Lista de tupla
>>> list((1, 2, 3))
[1, 2, 3]
# String a lista de caracteres
>>> list("hola")
['h', 'o', 'l', 'a']
# Error si la conversión no tiene sentido
>>> int("hola")
ValueError: invalid literal for int() with base 10: 'hola'
Por dentro: cómo funciona Python
Esta sección va más profundo. No necesitas memorizar esto, pero entenderlo te va a ahorrar horas de debugging y te va a hacer escribir mejor código.
Todo lo de esta sección está en el notebook con código ejecutable. Si prefieres aprender ejecutando, abre el notebook en Colab.
Todo es un objeto
En Python, absolutamente todo es un objeto: números, strings, listas, funciones, clases, módulos. Cada objeto tiene tres cosas:
| Propiedad | Qué es | Cómo verla |
|---|---|---|
| Identidad | Dirección en memoria (fija) | id(x) |
| Tipo | Qué operaciones soporta | type(x) |
| Valor | El dato en sí | print(x) |
Cuando escribes x = 42, Python crea un objeto int con valor 42 en memoria y pega la etiqueta x a ese objeto. La variable x no contiene el 42 — es un post-it pegado al objeto.
Esto significa que b = a no copia el valor — pega otra etiqueta al mismo objeto:
a = [1, 2, 3]
b = a # b apunta al MISMO objeto
a.append(4)
print(b) # [1, 2, 3, 4] — ¡b también cambió!
Esto se llama aliasing y es fuente de muchos bugs.
is vs ==
==compara valores — ¿tienen el mismo contenido?iscompara identidad — ¿son el mismo objeto en memoria?
lista1 = [1, 2, 3]
lista2 = [1, 2, 3] # mismo contenido, objeto DIFERENTE
lista1 == lista2 # True (mismo valor)
lista1 is lista2 # False (objetos diferentes)
Regla: siempre usa == para comparar valores. Solo usa is para comparar con None.
Mutabilidad e inmutabilidad
| Inmutables | Mutables |
|---|---|
int, float, str, bool, tuple |
list, dict, set |
| Reasignar = crear nuevo objeto | Modificar = mismo objeto cambia |
# Inmutable: x = x + 1 crea un NUEVO int
x = 10
id_antes = id(x)
x = x + 1
id_despues = id(x)
# id_antes != id_despues — son objetos diferentes
# Mutable: append modifica el MISMO objeto
nums = [1, 2, 3]
id_antes = id(nums)
nums.append(4)
# id(nums) == id_antes — mismo objeto
Copias: shallow vs deep
import copy
original = [1, 2, [3, 4]]
alias = original # NO copia — misma referencia
shallow = original.copy() # Copia la lista exterior, pero las sublistas son compartidas
deep = copy.deepcopy(original) # Copia TODO recursivamente
original[2].append(5)
# alias → [1, 2, [3, 4, 5]] (mismo objeto)
# shallow → [1, 2, [3, 4, 5]] (sublista compartida — ¡sorpresa!)
# deep → [1, 2, [3, 4]] (copia independiente)
Regla: si tu estructura tiene listas dentro de listas (o dicts dentro de listas), usa copy.deepcopy().
Paso de argumentos: pass by object reference
Python no es “pass by value” ni “pass by reference”. Es pass by object reference: el parámetro apunta al mismo objeto que el argumento.
- Con inmutables (int, str): parece “pass by value” porque no puedes modificar el objeto original
- Con mutables (list, dict): parece “pass by reference” porque modificas el objeto original
def agregar(lista, elem):
lista.append(elem) # modifica el MISMO objeto
mi_lista = [1, 2, 3]
agregar(mi_lista, 4)
print(mi_lista) # [1, 2, 3, 4] — ¡cambió!
El bug clásico: mutable default argument
# ❌ MAL: lista como valor por defecto
def agregar(elem, lista=[]):
lista.append(elem)
return lista
print(agregar(1)) # [1]
print(agregar(2)) # [1, 2] ← ¡se acumuló!
print(agregar(3)) # [1, 2, 3]
# ✅ BIEN: usar None
def agregar(elem, lista=None):
if lista is None:
lista = []
lista.append(elem)
return lista
Los valores por defecto se evalúan una sola vez cuando se define la función. Si es un objeto mutable, todas las llamadas comparten el mismo objeto.
Memoria: ¿cuánto pesa cada tipo?
Cada objeto Python tiene un header de 16 bytes mínimo: 8 bytes para el conteo de referencias (garbage collection) y 8 bytes para el puntero al tipo. Encima de eso va el valor.
import sys
sys.getsizeof(None) # 16 bytes — solo el header, sin datos
sys.getsizeof(0) # 24 bytes — header + metadata de tamaño
sys.getsizeof(42) # 28 bytes — header + 1 dígito de 30 bits
sys.getsizeof(2**100) # 40 bytes — header + 4 dígitos (crece)
sys.getsizeof(3.14) # 24 bytes — header + double de C (siempre fijo)
sys.getsizeof(True) # 28 bytes — bool es subclase de int
sys.getsizeof("") # 49 bytes — header + metadata de string
sys.getsizeof("hola") # 53 bytes — 49 + 4 caracteres ASCII
- int: CPython almacena enteros como arrays de dígitos de 30 bits. 42 cabe en 1 dígito (28 bytes), 2^100 necesita 4 dígitos (40 bytes). Crecen sin límite.
- float: siempre 24 bytes — un
doublede C (8 bytes) + header (16). No crece. - str: 49 bytes base + 1 byte por carácter ASCII. Caracteres Unicode usan 2 o 4 bytes cada uno.
- None: 16 bytes — el objeto más ligero posible.
Un int de C son 4 bytes. Un int de Python son 28+ bytes (7x más). Este es el precio de “todo es un objeto”. Para ciencia de datos usamos numpy/pandas que almacenan datos en arrays nativos de C, sin este overhead.
El intérprete CPython y bytecode
Cuando ejecutas python3 script.py:
- El código se compila a bytecode (archivos
.pyc) - La VM de Python ejecuta el bytecode instrucción por instrucción
Puedes ver el bytecode con dis:
import dis
def sumar(a, b):
return a + b
dis.dis(sumar)
# LOAD_FAST 0 (a)
# LOAD_FAST 1 (b)
# BINARY_ADD
# RETURN_VALUE
Esto explica por qué las list comprehensions son más rápidas que loops con .append() — generan menos instrucciones de bytecode y usan operaciones optimizadas en C.
El GIL (Global Interpreter Lock)
El GIL es un mutex en CPython que solo permite que un thread ejecute bytecode a la vez. Existe porque el conteo de referencias no es thread-safe.
Implicaciones prácticas:
- Threads NO dan paralelismo para trabajo CPU-bound
- Threads SÍ sirven para I/O-bound (red, disco) porque liberan el GIL durante la espera
- Para paralelismo CPU real: usa
multiprocessing
En ciencia de datos normalmente no te afecta porque numpy, pandas y similares liberan el GIL internamente.
Buenos y malos patrones
| Malo | Bueno | ¿Por qué? |
|---|---|---|
s += "x" en loop |
"".join(lista) |
O(n²) vs O(n) |
loop + .append() |
List comprehension | Menos bytecode |
x in lista (búsqueda) |
x in set |
O(n) vs O(1) |
list.insert(0, x) |
collections.deque.appendleft(x) |
O(n) vs O(1) |
range(len(x)) |
enumerate(x) |
Más legible |
def f(x=[]) |
def f(x=None) |
Bug vs correcto |
Errores comunes para principiantes
| Error | Causa | Solución |
|---|---|---|
IndentationError |
Indentación inconsistente | Usa siempre 4 espacios |
NameError: name 'x' is not defined |
Variable no existe | Verifica el nombre, ¿la definiste antes? |
TypeError: can only concatenate str to str |
Mezclaste tipos con + |
Usa f"" o convierte con str() |
IndexError: list index out of range |
Accediste a un índice que no existe | Verifica con len() primero |
KeyError: 'clave' |
La clave no existe en el dict | Usa .get() en vez de [] |
SyntaxError |
Olvidaste :, cerraste mal comillas, etc. |
Lee el mensaje de error, señala la línea |
En el REPL, ejecuta lo siguiente y predice el resultado antes de presionar Enter:
>>> type(42)
>>> type(42.0)
>>> type("42")
>>> type(True)
>>> type([1, 2, 3])
>>> type({"a": 1})
Ahora prueba estas operaciones:
>>> "3" + "4"
>>> 3 + 4
>>> "3" * 4
>>> 3 * "4"
¿Por qué "3" + "4" no da 7?
Crea un archivo notas.py con el siguiente código:
notas = [85, 92, 67, 74, 95, 88, 71, 60, 99, 43]
for nota in notas:
if nota >= 90:
letra = "A"
elif nota >= 80:
letra = "B"
elif nota >= 70:
letra = "C"
elif nota >= 60:
letra = "D"
else:
letra = "F"
print(f"Nota: {nota} → {letra}")
Ejecútalo con python3 notas.py.
Ahora modifícalo para que además cuente cuántos alumnos aprobaron (nota >= 60) y cuántos reprobaron:
python3 notas.py
# Nota: 85 → B
# Nota: 92 → A
# ...
# Aprobados: 9
# Reprobados: 1
Crea un archivo estudiantes.py:
estudiantes = [
{"nombre": "Ana", "edad": 22, "carrera": "Actuaría"},
{"nombre": "Luis", "edad": 21, "carrera": "Economía"},
{"nombre": "María", "edad": 23, "carrera": "Actuaría"},
{"nombre": "Carlos", "edad": 20, "carrera": "Matemáticas"},
{"nombre": "Sofía", "edad": 22, "carrera": "Economía"},
]
Usando for, if, listas y diccionarios, escribe código que:
- Imprima el nombre de cada estudiante y su carrera
- Cuente cuántos estudiantes hay por carrera (usa un diccionario para contar)
- Encuentre al estudiante más joven
- Filtre solo los estudiantes de Actuaría en una nueva lista
Crea un archivo funciones.py con las siguientes funciones:
es_par(n)— retornaTruesines par,Falsesi nopromedio(numeros)— recibe una lista de números y retorna su promediocontar_vocales(texto)— retorna cuántas vocales tiene un string
Prueba cada función:
print(es_par(4)) # True
print(es_par(7)) # False
print(promedio([10, 20, 30])) # 20.0
print(contar_vocales("murcielago")) # 5
Estoy aprendiendo Python y tengo el siguiente error:
[pega aquí el mensaje de error completo]
Mi código es:
[pega aquí tu código]
Explícame:
- ¿Qué significa este error en español simple?
- ¿En qué línea está el problema?
- ¿Cómo lo corrijo?
- ¿Cómo puedo evitar este error en el futuro?