AboutOpinionesBlogContacto

Software Crafters® 2025 | Creado con 🖤 para elevar el nivel de la conversación sobre programación en español | Legal

Tutorial de Python. Los fundamentos en 10 minutos

Guía completa y moderna de Python 3.12+. Aprende los fundamentos del lenguaje con ejemplos prácticos, type hints, pattern matching y las últimas características.

Miguel A. Gómez18 min read

En la newsletter hablo de cómo diseñar mejor software. O lo que es lo mismo: escribir código sostenible.

Al suscribirte comparto contigo los libros que más me han hecho crecer como dev —los que todo desarrollador serio debería leer al menos una vez.

Antes de empezar tengo que advertirte que ningún lenguaje de programación, por simple que sea, puede aprenderse en profundidad en tan poco tiempo, a no ser que se requiera de experiencia previa en otros lenguajes. Dominar la programación precisa de experiencia, lo cual a su vez requiere de un tiempo mínimo que permita afianzar las estructuras mentales necesarias para entender la secuencia lógica a seguir para desarrollar un programa o proyecto de software.

El objetivo de este artículo no es enseñar a programar, sino exponer en forma concisa los elementos más importantes del lenguaje Python 3.12+, sería algo así como una mezcla entre un tutorial y una cheatsheet. Incluyo ejemplos de código prácticos y ejecutables para cada concepto, con las características más modernas del lenguaje.

Características de Python

Python es un lenguaje multiparadigma, que soporta orientación a objetos, programación imperativa y programación funcional. Es interpretado, de tipado dinámico (aunque soporta type hints) y multiplataforma.

¿Por qué Python?

  • Sintaxis clara y legible
  • Extensa librería estándar
  • Gran ecosistema de paquetes
  • Comunidad activa y vibrante
  • Versatilidad (web, data science, automatización, IA, etc.)

Instalación y Setup

Para seguir este tutorial necesitas Python 3.10 o superior. Puedes descargarlo desde python.org.

Verifica tu instalación:

python3 --version
# Python 3.12.0 (o superior)

Para ejecutar código Python puedes usar:

  • El REPL interactivo:
    python3
    en la terminal
  • Archivos
    .py
    :
    python3 mi_script.py
  • Notebooks Jupyter para experimentación

Lo básico de Python

Sintaxis y comentarios

La sintaxis de Python es extremadamente "limpia". No se requiere ningún carácter que indique el final de una sentencia y los bloques se definen a través de la indentación del código.

# Esto es un comentario de una línea

"""
Esto es un comentario multilínea.
También se usa para docstrings.
"""

# Los bloques se definen por indentación (4 espacios)
if True:
    print("Este código está indentado")
    print("Pertenece al bloque if")

Operadores aritméticos

# Operaciones básicas
suma = 10 + 5        # 15
resta = 10 - 5       # 5
multiplicacion = 10 * 5  # 50
division = 10 / 3    # 3.333... (siempre retorna float)
division_entera = 10 // 3  # 3
modulo = 10 % 3      # 1
potencia = 2 ** 3    # 8

# Operadores de asignación compuesta
x = 10
x += 5  # x = x + 5  → 15
x -= 3  # x = x - 3  → 12
x *= 2  # x = x * 2  → 24
x /= 4  # x = x / 4  → 6.0

Operadores lógicos y de comparación

# Operadores de comparación
igual = 5 == 5           # True
diferente = 5 != 3       # True
mayor = 5 > 3            # True
menor = 3 < 5            # True
mayor_igual = 5 >= 5     # True
menor_igual = 3 <= 5     # True

# Operadores lógicos
verdadero = True and True   # True
falso = True and False      # False
cierto = True or False      # True
negacion = not True         # False

# Encadenamiento de comparaciones
x = 5
es_valido = 1 < x < 10  # True (equivalente a: 1 < x and x < 10)

Tipos de datos

Los tipos de datos básicos en Python son numéricos (enteros, reales, complejos), cadenas de texto, booleanos y None (nulo). La función

type()
nos devuelve el tipo:

# Números enteros
edad = 30
print(type(edad))  # <class 'int'>

# Números flotantes
altura = 1.75
print(type(altura))  # <class 'float'>

# Números complejos
complejo = 3 + 4j
print(type(complejo))  # <class 'complex'>

# Cadenas de texto
nombre = "Python"
apellido = 'Developer'
print(type(nombre))  # <class 'str'>

# Booleanos
activo = True
desactivado = False
print(type(activo))  # <class 'bool'>

# None (ausencia de valor)
vacio = None
print(type(vacio))  # <class 'NoneType'>

# Conversión entre tipos
numero_str = "42"
numero_int = int(numero_str)  # 42
numero_float = float(numero_str)  # 42.0
texto = str(100)  # "100"

Type Hints (Python 3.10+)

Python soporta anotaciones de tipo que mejoran la legibilidad y permiten detección de errores:

# Variables con type hints
nombre: str = "Miguel"
edad: int = 30
activo: bool = True

# Union types con | (Python 3.10+)
valor: int | float = 42
resultado: str | None = None

# Listas y diccionarios con tipos
numeros: list[int] = [1, 2, 3]
usuarios: dict[str, int] = {"Alice": 25, "Bob": 30}

Estructuras de control de flujo

Condicionales (if/elif/else)

edad = 18

if edad < 18:
    print("Eres menor de edad")
elif edad == 18:
    print("Acabas de ser mayor de edad")
else:
    print("Eres mayor de edad")

# Expresiones ternarias
mensaje = "Mayor" if edad >= 18 else "Menor"

# Operador walrus := (Python 3.8+)
# Asigna y evalúa en la misma expresión
if (n := len([1, 2, 3])) > 2:
    print(f"La lista tiene {n} elementos")

Match-Case (Python 3.10+)

Pattern matching estructural, similar a switch en otros lenguajes pero más poderoso:

def procesar_comando(comando):
    match comando:
        case "salir" | "exit" | "quit":
            return "Saliendo..."
        case "ayuda" | "help":
            return "Comandos disponibles: salir, ayuda, estado"
        case ["mover", direccion]:
            return f"Moviendo hacia {direccion}"
        case {"tipo": "usuario", "nombre": nombre}:
            return f"Hola, {nombre}"
        case _:
            return "Comando no reconocido"

print(procesar_comando("ayuda"))  # Comandos disponibles...
print(procesar_comando(["mover", "norte"]))  # Moviendo hacia norte

Bucles (for y while)

# For con range
for i in range(5):
    print(i)  # 0, 1, 2, 3, 4

# For con lista
frutas = ["manzana", "banana", "naranja"]
for fruta in frutas:
    print(fruta)

# For con enumerate (índice y valor)
for indice, fruta in enumerate(frutas):
    print(f"{indice}: {fruta}")

# For con diccionario
precios = {"manzana": 1.5, "banana": 0.8}
for fruta, precio in precios.items():
    print(f"{fruta}: ${precio}")

# While
contador = 0
while contador < 5:
    print(contador)
    contador += 1

# Break y continue
for i in range(10):
    if i == 3:
        continue  # Salta a la siguiente iteración
    if i == 7:
        break  # Sale del bucle
    print(i)

Funciones

Una función es un bloque de código reutilizable. En Python moderno usamos type hints para mayor claridad:

# Función básica
def saludar():
    print("¡Hola!")

saludar()  # ¡Hola!

# Función con parámetros y retorno
def sumar(a: int, b: int) -> int:
    return a + b

resultado = sumar(5, 3)  # 8

# Parámetros con valores por defecto
def saludar_persona(nombre: str, saludo: str = "Hola") -> str:
    return f"{saludo}, {nombre}!"

print(saludar_persona("Ana"))  # Hola, Ana!
print(saludar_persona("Ana", "Buenos días"))  # Buenos días, Ana!

# Múltiples valores de retorno
def dividir(a: int, b: int) -> tuple[int, int]:
    cociente = a // b
    resto = a % b
    return cociente, resto

q, r = dividir(10, 3)  # q=3, r=1

# Funciones lambda (anónimas)
cuadrado = lambda x: x ** 2
print(cuadrado(5))  # 25

# Lambda con múltiples parámetros
suma = lambda a, b: a + b
print(suma(3, 4))  # 7

Funciones con *args y **kwargs

# *args: argumentos posicionales variables (tupla)
def sumar_todos(*args: int) -> int:
    """Suma todos los argumentos recibidos"""
    return sum(args)

print(sumar_todos(1, 2, 3))  # 6
print(sumar_todos(1, 2, 3, 4, 5))  # 15

# **kwargs: argumentos con nombre variables (diccionario)
def mostrar_info(**kwargs: str) -> None:
    """Muestra información en formato clave: valor"""
    for clave, valor in kwargs.items():
        print(f"{clave}: {valor}")

mostrar_info(nombre="Ana", edad="25", ciudad="Madrid")
# nombre: Ana
# edad: 25
# ciudad: Madrid

# Combinando todo
def funcion_completa(
    obligatorio: str,
    opcional: str = "default",
    *args: int,
    **kwargs: str
) -> None:
    print(f"Obligatorio: {obligatorio}")
    print(f"Opcional: {opcional}")
    print(f"Args: {args}")
    print(f"Kwargs: {kwargs}")

funcion_completa("requerido", "custom", 1, 2, 3, extra="valor")

Manipulación de cadenas

Las cadenas en Python son inmutables y muy versátiles:

# Definición de cadenas
simple = 'Hola'
doble = "Mundo"
multilinea = """
Este es un texto
de múltiples líneas
"""

# F-strings (Python 3.6+) - Forma recomendada
nombre = "Python"
version = 3.12
mensaje = f"Estoy usando {nombre} {version}"

# F-strings con expresiones
x = 10
print(f"El doble de {x} es {x * 2}")

# F-strings con formato
precio = 19.99
print(f"Precio: ${precio:.2f}")  # Precio: $19.99

# F-string para debugging (Python 3.8+)
variable = 42
print(f"{variable=}")  # variable=42

# Operaciones con cadenas
texto = "python"
print(texto.upper())  # PYTHON
print(texto.capitalize())  # Python
print(texto.title())  # Python

# Búsqueda
frase = "Python es genial"
print("Python" in frase)  # True
print(frase.startswith("Python"))  # True
print(frase.endswith("genial"))  # True
print(frase.find("es"))  # 7 (índice donde empieza)

# División y unión
palabras = "uno,dos,tres".split(",")  # ['uno', 'dos', 'tres']
unido = "-".join(palabras)  # uno-dos-tres

# Reemplazo
texto = "Hola mundo"
nuevo = texto.replace("mundo", "Python")  # Hola Python

# Limpieza
espacios = "  texto con espacios  "
print(espacios.strip())  # "texto con espacios"

# Slicing (subcadenas)
texto = "Python"
print(texto[0])      # P
print(texto[-1])     # n
print(texto[0:3])    # Pyt
print(texto[::2])    # Pto (cada 2 caracteres)
print(texto[::-1])   # nohtyP (invertido)

# Raw strings (útil para regex)
ruta = r"C:\nueva\carpeta"  # No interpreta \n como nueva línea

Estructuras de datos (colecciones)

Listas

Las listas son colecciones ordenadas y mutables:

# Creación
numeros = [1, 2, 3, 4, 5]
mixta = [1, "dos", 3.0, True]
vacia = []

# Acceso
print(numeros[0])   # 1 (primer elemento)
print(numeros[-1])  # 5 (último elemento)

# Modificación
numeros[0] = 10
print(numeros)  # [10, 2, 3, 4, 5]

# Métodos comunes
numeros.append(6)        # Agregar al final
numeros.insert(0, 0)     # Insertar en posición
numeros.remove(10)       # Eliminar valor
elemento = numeros.pop() # Eliminar y retornar último
numeros.extend([7, 8])   # Extender con otra lista

# Operaciones
longitud = len(numeros)
hay_tres = 3 in numeros
maximo = max(numeros)
minimo = min(numeros)
suma = sum(numeros)

# Slicing
sublista = numeros[1:4]   # Elementos 1, 2, 3
invertida = numeros[::-1] # Lista invertida

# List comprehensions
cuadrados = [x ** 2 for x in range(5)]  # [0, 1, 4, 9, 16]
pares = [x for x in range(10) if x % 2 == 0]  # [0, 2, 4, 6, 8]

# Con if-else en comprehension
valores = [x if x > 0 else 0 for x in [-1, 2, -3, 4]]  # [0, 2, 0, 4]

# Ordenamiento
numeros.sort()  # Ordena in-place
ordenados = sorted(numeros)  # Retorna nueva lista ordenada
descendente = sorted(numeros, reverse=True)

Tuplas

Las tuplas son como listas pero inmutables (no se pueden modificar):

# Creación
coordenadas = (10, 20)
colores = ("rojo", "verde", "azul")
singleton = (42,)  # Nota la coma para tupla de 1 elemento

# Acceso (igual que listas)
x, y = coordenadas  # Desempaquetado
print(coordenadas[0])  # 10

# Las tuplas son inmutables
# coordenadas[0] = 15  # ¡ERROR! TypeError

# Uso común: retorno de múltiples valores
def obtener_dimensiones():
    return 1920, 1080

ancho, alto = obtener_dimensiones()

# Named tuples (más expresivas)
from collections import namedtuple

Punto = namedtuple('Punto', ['x', 'y'])
p = Punto(10, 20)
print(p.x, p.y)  # 10 20

Diccionarios

Los diccionarios almacenan pares clave-valor:

# Creación
persona = {
    "nombre": "Ana",
    "edad": 30,
    "ciudad": "Madrid"
}

# Otra forma
persona = dict(nombre="Ana", edad=30, ciudad="Madrid")

# Acceso
print(persona["nombre"])  # Ana
print(persona.get("edad"))  # 30
print(persona.get("telefono", "No disponible"))  # Valor por defecto

# Modificación
persona["edad"] = 31  # Actualizar
persona["email"] = "ana@example.com"  # Agregar nueva clave
del persona["ciudad"]  # Eliminar

# Métodos
claves = persona.keys()
valores = persona.values()
items = persona.items()  # Pares (clave, valor)

# Verificación
existe = "nombre" in persona  # True

# Iteración
for clave in persona:
    print(f"{clave}: {persona[clave]}")

for clave, valor in persona.items():
    print(f"{clave}: {valor}")

# Dictionary comprehension
cuadrados = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Filtrado
mayores = {k: v for k, v in persona.items() if isinstance(v, int)}

# Merge de diccionarios (Python 3.9+)
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
combinado = dict1 | dict2  # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

Sets (Conjuntos)

Los sets son colecciones no ordenadas de elementos únicos:

# Creación
numeros = {1, 2, 3, 4, 5}
letras = set("hello")  # {'h', 'e', 'l', 'o'} - duplicados eliminados

# Sets vacíos (no usar {} que crea diccionario)
vacio = set()

# Operaciones
numeros.add(6)
numeros.remove(1)  # Error si no existe
numeros.discard(10)  # No error si no existe

# Operaciones de conjuntos
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

union = a | b  # {1, 2, 3, 4, 5, 6}
interseccion = a & b  # {3, 4}
diferencia = a - b  # {1, 2}
simetrica = a ^ b  # {1, 2, 5, 6}

# Verificaciones
es_subconjunto = {1, 2} <= a  # True
es_superconjunto = a >= {1, 2}  # True

# Set comprehension
pares = {x for x in range(10) if x % 2 == 0}

# Uso común: eliminar duplicados
lista = [1, 2, 2, 3, 3, 3, 4]
unicos = list(set(lista))  # [1, 2, 3, 4]

Clases y objetos

Clases tradicionales

class Persona:
    """Representa una persona con nombre y edad"""
    
    # Variable de clase (compartida por todas las instancias)
    especie = "Homo sapiens"
    
    def __init__(self, nombre: str, edad: int):
        """Constructor de la clase"""
        self.nombre = nombre  # Atributo de instancia
        self.edad = edad
    
    def saludar(self) -> str:
        """Método de instancia"""
        return f"Hola, soy {self.nombre}"
    
    def cumplir_anos(self) -> None:
        """Incrementa la edad en 1"""
        self.edad += 1
    
    def __str__(self) -> str:
        """Representación en string"""
        return f"Persona(nombre={self.nombre}, edad={self.edad})"
    
    def __repr__(self) -> str:
        """Representación para debugging"""
        return f"Persona('{self.nombre}', {self.edad})"

# Uso
persona = Persona("Ana", 25)
print(persona.saludar())  # Hola, soy Ana
persona.cumplir_anos()
print(persona.edad)  # 26
print(persona)  # Persona(nombre=Ana, edad=26)

Herencia

class Empleado(Persona):
    """Hereda de Persona y añade salario"""
    
    def __init__(self, nombre: str, edad: int, salario: float):
        super().__init__(nombre, edad)  # Llama al constructor de Persona
        self.salario = salario
    
    def saludar(self) -> str:
        """Sobrescribe el método saludar"""
        return f"Hola, soy {self.nombre} y soy empleado"
    
    def aumentar_salario(self, porcentaje: float) -> None:
        self.salario *= (1 + porcentaje / 100)

empleado = Empleado("Carlos", 30, 50000)
print(empleado.saludar())  # Hola, soy Carlos y soy empleado
empleado.aumentar_salario(10)
print(f"Nuevo salario: ${empleado.salario}")  # $55000.0

Dataclasses (Python 3.7+)

Forma moderna y concisa de crear clases para datos:

from dataclasses import dataclass, field

@dataclass
class Producto:
    """Clase para representar un producto"""
    nombre: str
    precio: float
    cantidad: int = 0  # Valor por defecto
    etiquetas: list[str] = field(default_factory=list)
    
    def valor_total(self) -> float:
        """Calcula el valor total del inventario"""
        return self.precio * self.cantidad
    
    def __post_init__(self):
        """Se ejecuta después de __init__"""
        if self.precio < 0:
            raise ValueError("El precio no puede ser negativo")

# Uso
producto = Producto("Laptop", 999.99, 5, ["electrónica", "computadoras"])
print(producto)  # Producto(nombre='Laptop', precio=999.99, ...)
print(producto.valor_total())  # 4999.95

# Las dataclasses automáticamente generan:
# - __init__
# - __repr__
# - __eq__
# - Y más dependiendo de los parámetros

Properties

class Rectangulo:
    """Rectángulo con propiedades calculadas"""
    
    def __init__(self, ancho: float, alto: float):
        self._ancho = ancho
        self._alto = alto
    
    @property
    def area(self) -> float:
        """Propiedad de solo lectura"""
        return self._ancho * self._alto
    
    @property
    def ancho(self) -> float:
        return self._ancho
    
    @ancho.setter
    def ancho(self, valor: float) -> None:
        if valor <= 0:
            raise ValueError("El ancho debe ser positivo")
        self._ancho = valor
    
    def __str__(self) -> str:
        return f"Rectángulo({self._ancho}x{self._alto})"

rect = Rectangulo(5, 3)
print(rect.area)  # 15 (calculado automáticamente)
rect.ancho = 10
print(rect.area)  # 30

Features modernas de Python 3.10+

Pattern Matching avanzado

def procesar_dato(dato):
    """Ejemplos avanzados de pattern matching"""
    match dato:
        # Match literal
        case 0:
            return "Cero"
        
        # Match con condición (guard)
        case x if x < 0:
            return f"Negativo: {x}"
        
        # Match de tuplas
        case (x, y):
            return f"Par: ({x}, {y})"
        
        # Match de listas con captura
        case [first, *rest]:
            return f"Primero: {first}, Resto: {rest}"
        
        # Match de diccionarios
        case {"nombre": nombre, "edad": edad}:
            return f"{nombre} tiene {edad} años"
        
        # Match de clases
        case Persona(nombre=n, edad=e) if e >= 18:
            return f"{n} es mayor de edad"
        
        # Wildcard
        case _:
            return "Otro tipo de dato"

print(procesar_dato([1, 2, 3, 4]))  # Primero: 1, Resto: [2, 3, 4]

Union Types con |

# Antes (Python 3.9-)
from typing import Union, Optional

def procesar(valor: Union[int, str]) -> Optional[str]:
    pass

# Ahora (Python 3.10+)
def procesar(valor: int | str) -> str | None:
    if isinstance(valor, int):
        return str(valor)
    return valor

# Múltiples opciones
ResultadoAPI = dict[str, str] | list[str] | None

Type Parameters (Python 3.12+)

# Genéricos simplificados en Python 3.12+
def primero[T](items: list[T]) -> T | None:
    """Retorna el primer elemento o None"""
    return items[0] if items else None

# Clases genéricas
class Caja[T]:
    """Contenedor genérico"""
    def __init__(self, contenido: T):
        self.contenido = contenido
    
    def obtener(self) -> T:
        return self.contenido

caja_int = Caja(42)
caja_str = Caja("Hola")

F-strings mejorados

# Debugging con =
x, y = 10, 20
print(f"{x=}, {y=}, {x+y=}")  # x=10, y=20, x+y=30

# Formato de fechas
from datetime import datetime
ahora = datetime.now()
print(f"{ahora:%Y-%m-%d %H:%M:%S}")

# Alineación y relleno
nombre = "Python"
print(f"{nombre:>10}")  # Alineado a derecha
print(f"{nombre:^10}")  # Centrado
print(f"{nombre:<10}")  # Alineado a izquierda

# Números
pi = 3.14159265359
print(f"{pi:.2f}")  # 3.14
print(f"{1000000:,}")  # 1,000,000

Operador Walrus :=

# Asignar y usar en la misma expresión
# Útil en comprensiones y condicionales

# En condicionales
if (n := len([1, 2, 3, 4, 5])) > 3:
    print(f"Lista larga con {n} elementos")

# En while
import random
while (numero := random.randint(1, 10)) != 5:
    print(f"Intentando... {numero}")
print("¡Encontrado el 5!")

# En comprensiones
datos = [1, 2, 3, 4, 5]
resultado = [(x, cuadrado) for x in datos if (cuadrado := x**2) > 10]
# [(4, 16), (5, 25)]

Exception Groups (Python 3.11+)

# Agrupar múltiples excepciones
try:
    raise ExceptionGroup("Errores múltiples", [
        ValueError("Valor inválido"),
        TypeError("Tipo incorrecto"),
    ])
except* ValueError as e:
    print(f"Manejando ValueError: {e}")
except* TypeError as e:
    print(f"Manejando TypeError: {e}")

Excepciones

Las excepciones permiten manejar errores de forma controlada:

# Try-except básico
try:
    resultado = 10 / 0
except ZeroDivisionError:
    print("No se puede dividir por cero")

# Múltiples excepciones
try:
    valor = int("abc")
except ValueError:
    print("Error de conversión")
except TypeError:
    print("Error de tipo")

# Capturar múltiples tipos
try:
    # código que puede fallar
    pass
except (ValueError, TypeError) as e:
    print(f"Error: {e}")

# Try-except-else-finally
try:
    archivo = open("datos.txt", "r")
    contenido = archivo.read()
except FileNotFoundError:
    print("Archivo no encontrado")
else:
    # Se ejecuta si no hubo excepciones
    print(f"Archivo leído: {len(contenido)} caracteres")
finally:
    # Siempre se ejecuta
    if 'archivo' in locals():
        archivo.close()

# Context managers (recomendado para archivos)
try:
    with open("datos.txt", "r") as archivo:
        contenido = archivo.read()
except FileNotFoundError:
    print("Archivo no encontrado")

# Lanzar excepciones
def validar_edad(edad: int) -> None:
    if edad < 0:
        raise ValueError("La edad no puede ser negativa")
    if edad > 150:
        raise ValueError("Edad no realista")

# Excepciones personalizadas
class ErrorValidacion(Exception):
    """Excepción personalizada para errores de validación"""
    pass

class ErrorEdadInvalida(ErrorValidacion):
    """Error cuando la edad no es válida"""
    def __init__(self, edad: int, mensaje: str = "Edad inválida"):
        self.edad = edad
        self.mensaje = f"{mensaje}: {edad}"
        super().__init__(self.mensaje)

try:
    raise ErrorEdadInvalida(-5)
except ErrorEdadInvalida as e:
    print(e.mensaje)  # Edad inválida: -5

Debugging moderno

Usando breakpoint()

Python 3.7+ incluye la función

breakpoint()
que es más flexible que
pdb.set_trace()
:

def calcular_factorial(n: int) -> int:
    resultado = 1
    for i in range(1, n + 1):
        resultado *= i
        breakpoint()  # El depurador se detendrá aquí
    return resultado

# Al ejecutar, se abre el debugger interactivo
# Comandos básicos de pdb:
# n (next): siguiente línea
# s (step): entrar en función
# c (continue): continuar ejecución
# p variable: imprimir variable
# l (list): mostrar código
# q (quit): salir del debugger

Herramientas de debugging

# ipdb: Mejor que pdb con autocompletado y colores
# pip install ipdb
import ipdb
ipdb.set_trace()

# pudb: Debugger visual en terminal
# pip install pudb
import pudb
pudb.set_trace()

# Logging en lugar de prints
import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

logger.debug("Mensaje de debug")
logger.info("Información general")
logger.warning("Advertencia")
logger.error("Error")
logger.critical("Error crítico")

# Assert para validaciones en desarrollo
def calcular_promedio(numeros: list[float]) -> float:
    assert len(numeros) > 0, "La lista no puede estar vacía"
    return sum(numeros) / len(numeros)

Guía de estilo - PEP 8

Python tiene convenciones de estilo documentadas en PEP 8:

Convenciones principales

# Naming conventions
variable_nombre = "snake_case para variables y funciones"
CONSTANTE = "MAYÚSCULAS para constantes"
class MiClase:  # PascalCase para clases
    pass

# Indentación: 4 espacios
def funcion():
    if True:
        print("4 espacios por nivel")

# Longitud de línea: 79 caracteres (88 para Black)
texto_largo = (
    "Para líneas largas, usar paréntesis "
    "para continuar en la siguiente línea"
)

# Imports
# 1. Librería estándar
import os
import sys

# 2. Librerías de terceros
import numpy as np
import pandas as pd

# 3. Imports locales
from mi_modulo import mi_funcion

# Espaciado
x = 5  # Espacio alrededor de operadores
lista = [1, 2, 3]  # Espacio después de comas
funcion(a, b, c)  # No espacio antes de paréntesis

# Comparaciones
# Bien
if variable is None:
    pass

if valor in lista:
    pass

# Mal
if variable == None:  # Usar 'is' para None
    pass

# Type hints
def funcion(param: str, otro: int = 0) -> bool:
    return True

# Docstrings
def mi_funcion(x: int) -> int:
    """
    Descripción breve de la función.
    
    Args:
        x: Descripción del parámetro
    
    Returns:
        Descripción del valor de retorno
    
    Raises:
        ValueError: Cuándo se lanza esta excepción
    """
    return x * 2

Herramientas de formateo y linting

# Black: Formateador automático
pip install black
black mi_script.py

# Ruff: Linter moderno y rápido (reemplaza flake8, pylint, etc.)
pip install ruff
ruff check mi_script.py
ruff format mi_script.py

# mypy: Verificación de tipos estáticos
pip install mypy
mypy mi_script.py

# isort: Ordenar imports automáticamente
pip install isort
isort mi_script.py

Recursos adicionales

Documentación oficial

  • Python.org - Sitio oficial
  • Python Docs - Documentación completa
  • PEP Index - Python Enhancement Proposals
  • PyPI - Repositorio de paquetes

Herramientas recomendadas

Gestión de entornos:

  • venv
    : Incluido en Python, para entornos virtuales
  • poetry
    : Gestión moderna de dependencias
  • pyenv
    : Gestión de múltiples versiones de Python

Desarrollo:

  • VS Code con Python extension
  • PyCharm (IDE completo)
  • Jupyter Notebooks (exploración y análisis)

Testing:

  • pytest
    : Framework de testing moderno
  • unittest
    : Incluido en la librería estándar
  • hypothesis
    : Property-based testing

Calidad de código:

  • black
    : Formateador automático
  • ruff
    : Linter ultrarrápido
  • mypy
    : Type checker
  • pre-commit
    : Git hooks para calidad

Siguientes pasos

  1. Practica con proyectos: La mejor forma de aprender es haciendo
  2. Lee código de otros: Explora proyectos open source en GitHub
  3. Profundiza en áreas específicas:
    • Web: Django, FastAPI, Flask
    • Data Science: pandas, numpy, scikit-learn
    • Automatización: scripting, APIs, web scraping
    • Testing: pytest, coverage, mocks
  4. Únete a la comunidad:
    • Python Discord
    • r/Python en Reddit
    • Meetups locales de Python

Resumen

Este tutorial ha cubierto los fundamentos de Python 3.12+ con ejemplos prácticos de:

  • ✅ Sintaxis básica y operadores
  • ✅ Tipos de datos y type hints
  • ✅ Estructuras de control (incluyendo match-case)
  • ✅ Funciones con anotaciones modernas
  • ✅ Estructuras de datos (listas, tuplas, dicts, sets)
  • ✅ POO con clases y dataclasses
  • ✅ Features modernas de Python 3.10-3.12
  • ✅ Manejo de excepciones
  • ✅ Debugging y herramientas
  • ✅ Convenciones de estilo

Python es un lenguaje en constante evolución. Mantente actualizado con las nuevas versiones y sigue practicando para convertirte en un Pythonista experto.

Si te ha gustado el artículo, valora y comparte en tus redes sociales.

¿Quiéres leer más artículos como éste? Pues suscríbete a la newsletter

Quizás también te interese

Xamarin Forms, apps nativas multiplataforma. Introducción

Servidor de desarrollo Django con Vagrant y Ansible