Skip to content
This page has been auto-translated and may contain errors.View in English

Módulos y la biblioteca estándar

docs.scrimba.com

Python viene con una enorme colección de herramientas listas para usar: aleatoriedad, matemáticas, fechas, rutas de archivos y mucho más. Estas herramientas viven en módulos, y las traes a tu código con import. Ya has usado import json en el capítulo Archivos y excepciones. Este capítulo cubre completamente las importaciones e introduce las partes más útiles de la biblioteca estándar.

La biblioteca estándar de Python proporciona soluciones probadas y documentadas para problemas comunes. Los módulos son la unidad de organización del código: cada archivo es un módulo, cada directorio con __init__.py es un paquete. El sistema import encuentra módulos, los compila si es necesario, y los almacena en caché en sys.modules para que solo se carguen una vez.

Un import hace trabajo real: Python ubica el módulo, lo ejecuta una vez, y luego lo almacena en caché en sys.modules (el diccionario de cada módulo ya cargado) para que un segundo import del mismo nombre sea una búsqueda rápida, no una re-ejecución. import requests vincula el objeto módulo al nombre requests en tu namespace actual (la tabla de nombres visibles en ese punto del código). from requests import get ejecuta la misma carga pero vincula solo get. Las partes que rinden en proyectos reales son dónde busca Python (sys.path), cómo se vuelven importables los directorios (__init__.py), y cómo evitar importaciones circulares, todo cubierto a continuación.

Importar módulos

La importación más simple trae un módulo completo y te permite usar su contenido con notación de punto. También puedes importar nombres específicos de un módulo para usarlos directamente sin el prefijo. Los alias acortan nombres largos.

import module vincula el objeto módulo al nombre module en el alcance actual. from module import name vincula solo name. Los alias (import module as alias) son comunes con bibliotecas de terceros. Evita from module import *: contamina el namespace y hace que sea poco claro de dónde provienen los nombres.

from module import name no salta ningún trabajo: Python todavía carga y ejecuta el módulo completo, luego vincula solo el nombre que pediste. La trampa que debes conocer es la importación circular, donde dos módulos intenta importar el uno al otro mientras aún se cargan, así que uno de ellos ve un módulo semi-construido y falta un nombre. Las soluciones habituales son mover el import ofensivo dentro de la función que lo necesita (para que se ejecute después de que ambos módulos terminen de cargarse) o extraer el código compartido en un tercer módulo que ambos puedan importar. Cuando necesitas importar un módulo cuyo nombre solo conoces en tiempo de ejecución, importlib.import_module("name") toma el nombre como una cadena.

python
import math

math.sqrt(16)     # 4.0
math.pi           # 3.141592653589793
math.floor(3.9)   # 3
math.ceil(3.1)    # 4

Importa nombres específicos de un módulo para que puedas usarlos directamente:

python
from math import sqrt, pi

sqrt(16)    # 4.0 (no se necesita el prefijo "math.")
pi          # 3.141592653589793

Dale a un módulo o nombre un alias para acortarlo:

python
import math as m

m.sqrt(16)    # 4.0

from math import sqrt as square_root
square_root(25)    # 5.0

Los alias son comunes con bibliotecas populares de terceros (import numpy as np, import pandas as pd). Para módulos de la biblioteca estándar, prefiere usar el nombre completo; hace que el código sea más legible.

JunoImportar módulosimport math trae el módulo completo, luego accedes dentro con un punto: math.sqrt(16). from math import sqrt toma un nombre para que puedas omitir el prefijo. La palabra clave as renombra una importación larga, pero para contenido de la biblioteca estándar el nombre completo se lee más claro.
JunoImportar módulosimport module vincula el objeto módulo, from module import name vincula un nombre, import module as alias lo renombra. Salta from module import *: arroja todos los nombres a tu alcance y oculta de dónde viene cada uno.
JunoImportar módulosfrom module import name todavía carga el módulo completo, solo mantiene un nombre. El problema que eventualmente golpearás es la importación circular, donde dos módulos se importan el uno al otro a mitad de la carga: mueve ese `import` dentro de la función, o divide el código compartido en un tercer módulo. importlib.import_module() está ahí cuando el nombre solo se conoce en tiempo de ejecución.

random

El módulo random genera números aleatorios y hace selecciones aleatorias. Úsalo para juegos, simulaciones, muestreo aleatorio y cualquier otra cosa que necesite impredecibilidad. Establecer una semilla hace que los resultados sean reproducibles: la misma semilla produce la misma secuencia cada vez.

random usa un generador de números pseudoaleatorios Mersenne Twister. La semilla determina la secuencia completa; la misma semilla siempre produce la misma salida. .choice() elige un elemento, .choices() elige con reemplazo, .sample() elige sin reemplazo. .shuffle() modifica la lista en su lugar y devuelve None.

random produce números pseudoaleatorios: se ven aleatorios pero provienen de una fórmula fija impulsada por un valor inicial (la semilla), por lo que la misma semilla da la misma secuencia en cada máquina, que es lo que hace que una prueba sea reproducible. La regla que importa en producción: nunca uses random para nada que una persona pudiera explotar, como contraseñas, tokens de sesión, o códigos de restablecimiento de contraseña. Es predecible por diseño, así que un atacante que vea suficiente salida puede adivinar el resto. Para esos casos, recurre al módulo secrets en su lugar, que extrae de la aleatoriedad segura del sistema operativo. random.SystemRandom() te da esa misma fuente segura detrás de los nombres de método random familiares si prefieres no cambiar de API.

python
import random

random.random()              # float entre 0 y 1 (exclusivo)
random.randint(1, 10)        # entero de 1 a 10 (ambos inclusivos)
random.uniform(1.0, 10.0)    # float entre 1.0 y 10.0

colores = ["rojo", "verde", "azul"]
random.choice(colores)       # elige un elemento
random.choices(colores, k=3) # elige k elementos (con reemplazo)
random.sample(colores, k=2)  # elige k elementos (sin reemplazo)

numeros = [1, 2, 3, 4, 5]
random.shuffle(numeros)      # mezcla en su lugar, devuelve None

Para resultados reproducibles (útil en pruebas y ciencia de datos), establece una semilla antes de generar:

python
random.seed(42)
random.randint(1, 100)   # siempre el mismo valor para la semilla 42

La misma semilla produce la misma secuencia cada vez, en cualquier máquina.

Junorandomrandom.choice() elige un elemento de una lista, random.randint(1, 10) da un número entero en un rango, final incluido. ¿Quieres los mismos resultados cada ejecución, digamos para una prueba? Llama a random.seed() primero y la secuencia se bloquea.
Junorandom.choice() elige uno, .choices() elige con reemplazo, .sample() sin reemplazo. .shuffle() reordena la lista en su lugar y devuelve None, así que nunca escribas nums = random.shuffle(nums). La semilla fija toda la secuencia, que es cómo haces que una prueba aleatoria sea repetible.
Junorandomrandom es pseudoaleatorio y predecible por diseño, así que está bien para juegos y pruebas pero está mal para cualquier cosa que un atacante pudiera explotar. Usa secrets para tokens, contraseñas, y códigos de restablecimiento. Mantén random.seed() para los casos reproducibles donde realmente quieres que la secuencia se repita.

math

El módulo math añade operaciones matemáticas más avanzadas más allá de los operadores aritméticos básicos que conociste en el capítulo Números. Raíces cuadradas, potencias, logaritmos, trigonometría, y valores especiales como pi e infinito están todos aquí.

math proporciona implementaciones de nivel C de funciones matemáticas estándar. Ten en cuenta que math.pow() siempre devuelve un float, mientras que el operador ** de Python devuelve int para bases y exponentes enteros. math.log(x, base) calcula el logaritmo a cualquier base; math.log(x) calcula el logaritmo natural.

El detalle que causa problemas en código real es nan (abreviatura de "no es un número", el float que obtienes de resultados indefinidos como 0/0 o un análisis fallido) e inf (infinito). Un nan nunca es igual a nada, ni siquiera a sí mismo, por lo que x == float("nan") siempre es False. Eso significa que no puedes probarlo con ==; usa math.isnan(x), e math.isinf(x) para infinito. El otro hábito que vale la pena mantener: prefiere math.isclose(a, b) sobre a == b al comparar dos floats, ya que el redondeo de punto flotante hace que la igualdad exacta no sea confiable. Para matemáticas sobre matrices completas de números en lugar de uno a la vez, numpy es la herramienta estándar, aunque está fuera de esta guía.

python
import math

math.sqrt(25)        # 5.0
math.pow(2, 10)      # 1024.0 (igual que 2 ** 10 pero siempre devuelve float)
math.log(100, 10)    # 2.0 (log base 10)
math.log(math.e)     # 1.0 (log natural)

math.sin(math.pi / 2)   # 1.0
math.cos(0)             # 1.0

math.ceil(3.2)    # 4
math.floor(3.9)   # 3
math.trunc(3.9)   # 3 (igual que int() para positivos)

math.inf          # infinito
math.isnan(float("nan"))   # True
math.isinf(math.inf)       # True
Junomathmath cubre todo más allá de + - * /: raíces cuadradas, potencias, logs, trig, más constantes como math.pi. Recurre a él en el momento en que un cálculo necesita más que los operadores básicos, y omites escribir la fórmula tú mismo.
Junomathmath tiene raíces cuadradas, logs, y trig. Una cosa a observar: math.pow(2, 10) siempre devuelve un float, mientras que 2 ** 10 se mantiene como `int` para entradas enteras. math.log(x, base) toma cualquier base, math.log(x) es el log natural.
Junomath Las trampas son nan e inf: nan nunca se iguala a sí mismo, así que prueba con math.isnan(), no ==. Y compara floats con math.isclose() en lugar de ==, ya que el redondeo hace que la igualdad exacta no sea confiable. Para matrices completas de números, ese es territorio de numpy.

datetime

El módulo datetime maneja fechas y horas. datetime.now() te da la fecha y hora actual. strftime() la formatea como una cadena. strptime() analiza una cadena en un datetime. timedelta representa una duración que puedes sumar o restar.

datetime, date, y timedelta son las clases principales. strftime() formatea un datetime como una cadena usando códigos de formato. strptime() analiza una cadena dado un patrón de formato. timedelta soporta aritmética: puedes sumar o restar duraciones de fechas y comparar datetimes con <, >, -.

La trampa de producción es que un datetime.now() simple es ingenuo: no tiene zona horaria adjunta, por lo que en realidad no sabe qué parte del mundo representa. Dos datetimes ingenuos de diferentes máquinas se ven comparables pero silenciosamente no lo son, y esa ambigüedad causa errores de horas desfasadas. La solución es trabajar con datetimes conscientes (los que llevan una zona horaria), y estandarizar en UTC para cualquier cosa almacenada o enviada entre sistemas: datetime.now(timezone.utc). Convierte a hora local solo en el borde, cuando se la muestres a una persona. Usa el módulo zoneinfo para zonas nombradas como "Europe/Oslo", que maneja cambios de horario de verano para ti. Una regla más: nunca midas el tiempo transcurrido con diferencias de datetime.now(), porque el reloj del sistema puede saltar (una sincronización o un cambio manual); usa time.perf_counter(), un contador que solo avanza hacia adelante, para duraciones.

python
from datetime import datetime, date, timedelta

now = datetime.now()             # fecha y hora actual
today = date.today()             # solo la fecha actual

print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)

# Formato
print(now.strftime("%Y-%m-%d"))           # "2024-01-15"
print(now.strftime("%d %B %Y, %H:%M"))   # "15 de enero de 2024, 09:42"

# Análisis
deadline = datetime.strptime("2024-12-31", "%Y-%m-%d")

# Aritmética
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
diff = deadline - now
print(f"{diff.days} días hasta la fecha límite")

Códigos comunes de strftime:

CódigoSignificadoEjemplo
%YAño de 4 dígitos2024
%mMes (con relleno de cero)01
%dDía (con relleno de cero)15
%HHora (24h)09
%MMinuto42
%BNombre completo del mesenero
Junodatetimedatetime.now() te da la fecha y hora actuales. strftime() la convierte en una cadena con códigos como %Y-%m-%d, y strptime() va en la otra dirección, analizando una cadena nuevamente. timedelta es una duración que puedes sumar o restar, así que today + timedelta(days=1) es mañana.
Junodatetimedatetime, date, y timedelta son el núcleo. strftime() formatea con códigos, strptime() analiza con un patrón coincidente. Las fechas hacen aritmética: resta dos y obtienes un timedelta, suma un timedelta para avanzar o retroceder.
Junodatetime Un `datetime.now()` simple es ingenuo, sin zona horaria, lo que silenciosamente se rompe una vez que dos máquinas están involucradas. Almacena y pasa UTC con datetime.now(timezone.utc), convierte a local solo para mostrar, y apóyate en zoneinfo para zonas nombradas. Y mide el tiempo transcurrido con time.perf_counter(), nunca diferencias de now(), ya que el reloj de pared puede saltar.

os y pathlib

pathlib es la forma moderna de trabajar con rutas de archivos. Los objetos Path te permiten construir, inspeccionar y navegar rutas usando el operador /. os da acceso a variables de entorno y operaciones de SO de nivel inferior. Prefiere pathlib para código nuevo.

pathlib.Path representa rutas del sistema de archivos como objetos con métodos para consultar y navegar. El operador / une componentes de ruta de forma limpia, manejando separadores específicos del SO automáticamente. os.environ es un objeto similar a un diccionario para variables de entorno; os.environ.get("KEY", "default") es seguro para variables faltantes.

Path se adapta al sistema operativo automáticamente, así que el mismo código une rutas con el separador correcto en cada máquina: escribe Path("data") / "file.csv", nunca cadenas construidas a mano como "data/" + name que se rompan en Windows. Dos notas prácticas. Primero, .glob(), .rglob(), e .iterdir() devuelven generadores (producen entradas una a la vez en lugar de construir una lista completa), así que envuélvelos en list() si necesitas hacer un bucle dos veces o conocer el conteo por adelantado; para un directorio enorme, iterar de forma perezosa es el punto. Segundo, algunas bibliotecas más antiguas todavía esperan una cadena simple en lugar de un objeto Path; cuando una rechaza un Path, pasa str(path) y continúa. Para leer una variable de entorno, siempre usa os.environ.get("KEY", "default") en lugar de os.environ["KEY"], porque la forma indexada genera KeyError cuando la variable no está establecida, que es el caso común en un despliegue nuevo.

python
from pathlib import Path

p = Path("data/reportes")

p.exists()           # True si la ruta existe
p.is_dir()           # True si es un directorio
p.is_file()          # True si es un archivo

p.mkdir(parents=True, exist_ok=True)   # crear directorios

for f in p.glob("*.csv"):              # todos los archivos CSV en el directorio
    print(f.name)                      # el nombre del archivo

report = p / "reporte_ene.csv"         # operador / une rutas
report.stem       # "reporte_ene" (nombre sin extensión)
report.suffix     # ".csv"
report.parent     # Path("data/reportes")

content = report.read_text()           # leer contenidos del archivo directamente
report.write_text("nuevo contenido\n")    # escribir directamente

Para el módulo os:

python
import os

os.getcwd()                        # directorio de trabajo actual
os.listdir(".")                    # listar contenidos del directorio
os.path.exists("data.txt")        # True si la ruta existe
os.path.join("data", "file.txt")  # "data/file.txt" (multiplataforma)
os.environ.get("HOME")            # leer una variable de entorno

Prefiere pathlib para código nuevo. Usa os cuando necesites variables de entorno u trabajar con APIs más antiguas que esperan cadenas.

Junoos y pathlibpathlib.Path trata una ruta de archivo como un objeto que puedes revisar: .exists(), .read_text(), .write_text(), .glob(). El operador `/` une piezas, así que p / "report.csv" se lee como una ruta real. Recurre a pathlib sobre el más antiguo os.path en código nuevo.
Junoos y pathlibPath une con `/` y maneja el separador del SO para ti, así que las rutas se mantienen portátiles. os.environ es similar a un diccionario para variables de entorno, y os.environ.get("KEY", "default") es la lectura segura, no explota en una clave faltante.
Junoos y pathlib Construye rutas con Path y `/`, nunca cadenas pegadas que se rompan en Windows. .glob() y amigos devuelven generadores, así que list() ellos si necesitas hacer un bucle dos veces. Y lee variables de entorno con os.environ.get(key, default): la forma de corchete genera KeyError en el momento en que una variable no está establecida, que es exactamente el caso de despliegue nuevo.

timeit

timeit mide cuánto tiempo tarda el código en ejecutarse. Es útil cuando quieres comparar dos enfoques y elegir el más rápido. Ejecuta el código muchas veces para obtener una medición estable.

timeit.timeit(stmt, setup, number) cronometra stmt ejecutándolo number veces y devolviendo el tiempo total transcurrido en segundos. La cadena setup se ejecuta una sola vez antes del bucle cronometrado. Divide el resultado por number para obtener el tiempo por llamada. Más repeticiones reducen el ruido de la programación del sistema.

timeit es la herramienta correcta para un microbenchmark: cronometrar un pequeño fragmento de forma aislada, ejecutado muchas veces para que el ruido de programación se promedie. Reduce ese ruido para ti, pero una sola ejecución es aún poco confiable, así que toma el mejor de varios intentos en lugar de un solo número. El error que debes evitar es usarlo para encontrar una ralentización en un programa completo: solo cronometra el fragmento que le pasas, así que no puede decirte qué función es realmente lenta. Para eso, usa cProfile, que ejecuta tu programa real e informa dónde fue el tiempo. Regla de oro: timeit para comparar dos implementaciones, cProfile para encontrar el cuello de botella en primer lugar.

python
import timeit

# Cronometrar una única declaración
timeit.timeit("sum(range(1000))", number=10000)

# Cronometrar un bloque más complejo
setup = "data = list(range(1000))"
code = "[x * 2 for x in data]"
time = timeit.timeit(code, setup=setup, number=10000)
print(f"{time:.4f} segundos para 10,000 ejecuciones")

number es cuántas veces repetir. Más repeticiones dan una medición más estable.

Junotimeittimeit.timeit() cronometra un pedazo de código ejecutándolo una y otra vez, luego divides por el conteo de ejecución para un número estable por llamada. Es cómo resuelves "cuál de estos dos es más rápido" en lugar de adivinar. Una ejecución es demasiado ruidosa para confiar, así que deja que haga un bucle.
Junotimeittimeit.timeit(stmt, setup, number) ejecuta stmt ese número de veces y devuelve los segundos totales; la cadena setup se ejecuta una sola vez antes del bucle. Divide por number para el tiempo por llamada, y aumenta las repeticiones para ahogar el ruido de la programación.
Junotimeittimeit es para microbenchmarks: comparar dos pequeños fragmentos, mejor de varios en lugar de una ejecución única y ruidosa. No puede decirte qué función en un programa real es lenta, ya que solo cronometra lo que le pasas. Usa cProfile para encontrar el cuello de botella, luego timeit para comparar arreglos.

string

El módulo string proporciona constantes de cadena preformadas para letras, dígitos y puntuación. Útil cuando necesitas verificar caracteres o generar cadenas aleatorias a partir de un alfabeto específico.

Las constantes del módulo string (ascii_letters, digits, punctuation) son cadenas simples que puedes indexar, iterar, o usar con in. Combinarlas con random.choices() es la forma estándar de generar tokens aleatorios o contraseñas.

Estas constantes son cadenas ordinarias, así que verificar char in string.ascii_letters escanea toda la cadena cada vez. Ese escaneo es O(n): el trabajo crece con la longitud de la cadena, bien una vez, despilfarro en un bucle apretado. Si estás probando membresía repetidamente, construye un set una sola vez (letters = set(string.ascii_letters)) y verifica contra eso, ya que una búsqueda de conjunto se mantiene rápida sin importar cuántas entradas contenga. El otro fragmento útil aquí es string.Template, que hace sustitución simple con $name: cuando una plantilla viene de entrada de usuario o un archivo de configuración, es más seguro que pasar texto no confiable a una f-string o str.format(), porque no puede alcanzar tus variables o llamar métodos.

python
import string

string.ascii_lowercase   # "abcdefghijklmnopqrstuvwxyz"
string.ascii_uppercase   # "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
string.ascii_letters     # ambos combinados
string.digits            # "0123456789"
string.punctuation       # todos los caracteres de puntuación

Útil cuando necesitas verificar caracteres o generar cadenas aleatorias:

python
import string, random

chars = string.ascii_letters + string.digits
password = "".join(random.choices(chars, k=12))
Junostring El módulo string te entrega conjuntos de caracteres preformados: string.ascii_letters, string.digits, string.punctuation. Son cadenas simples, así que puedes hacer bucles sobre ellas o muestrear de ellas. Práctico siempre que necesites un alfabeto conocido para construir o verificar.
Junostringstring.ascii_letters, string.digits y amigos son cadenas simples que puedes indexar, iterar, o probar con in. Combínalos con random.choices() y tienes la receta estándar para un token o contraseña aleatoria. Ten en cuenta que random no es seguro para secretos reales.
Junostring Las constantes son cadenas simples, así que char in string.ascii_letters escanea todo cada llamada: construye un set una sola vez si estás verificando en un bucle. Y cuando una plantilla viene de entrada de usuario, string.Template con $name es más seguro que una f-string, ya que no puede alcanzar tus variables.

Crear tus propios módulos

Cualquier archivo de Python es un módulo. Para usarlo desde otro archivo, impórtalo por el nombre del archivo (sin .py). Puedes importar el módulo completo y usar su contenido con notación de punto, o importar nombres específicos directamente.

Cuando Python importa un módulo, ejecuta el archivo de arriba a abajo una sola vez y almacena el resultado en caché en sys.modules. Las importaciones posteriores del mismo módulo devuelven el objeto almacenado en caché sin volver a ejecutar el archivo. Para proyectos más grandes, los módulos se organizan en paquetes: directorios con un archivo __init__.py.

Cuando escribes import utils, Python busca en los directorios en sys.path en orden, comenzando con la carpeta del script que ejecutaste. Esta es la fuente del error de importación más común "funciona en mi máquina": ejecutar un archivo desde un directorio de trabajo diferente y la carpeta que espera puede no estar en la ruta, así que la importación falla. La solución confiable es ejecutar el proyecto como un paquete (python -m mypackage.main) en lugar de señalar Python a un archivo suelto. Un paquete es un directorio de módulos con un archivo __init__.py (puede estar vacío; su presencia es lo que le dice a Python que la carpeta es importable). Dentro de un paquete, una importación relativa como from . import helpers significa "desde este mismo paquete", que mantiene las importaciones funcionando incluso si el paquete se renombra o se mueve. Un borde afilado: importlib.reload() vuelve a ejecutar un módulo, pero cualquier nombre que ya importaste todavía apunta a la versión antigua, así que recargar es poco confiable para cualquier cosa más allá de un experimento rápido en el REPL.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

PI = 3.14159
python
# main.py
import utils

utils.clamp(150, 0, 100)   # 100
utils.PI                    # 3.14159

from utils import clamp
clamp(50, 0, 100)           # 50

Python encuentra el módulo buscando en el mismo directorio que el archivo de importación (y algunos otros lugares). Para proyectos más grandes, los módulos se organizan en paquetes: directorios con un archivo __init__.py.

JunoCrear tus propios módulos Cualquier archivo .py es ya un módulo: impórtalo por su nombre de archivo sin .py, y sus funciones y variables son tuyas para usar. import utils luego utils.clamp(...), o extrae un nombre con from utils import clamp. Así es cómo divides un programa en crecimiento entre archivos.
JunoCrear tus propios módulos Importar un módulo lo ejecuta de arriba a abajo una sola vez, luego lo almacena en caché en sys.modules, así que las importaciones posteriores devuelven el mismo objeto sin volver a ejecutar el archivo. Mantén el código de nivel superior ligero por eso, cualquier cosa pesada se ejecuta en tiempo de importación. Agrupa módulos relacionados en un paquete: una carpeta con __init__.py.
JunoCrear tus propios módulos Las importaciones se resuelven a través de sys.path, comenzando con la carpeta del script, que es por qué ejecutar un archivo suelto desde el directorio equivocado rompe la importación. Ejecutalo como un paquete en su lugar: python -m pkg.main. Y no confíes en importlib.reload() más allá de pokes rápidos en el REPL, ya que los nombres que ya importaste siguen apuntando a la versión antigua.

__name__ == "__main__"

Cuando Python ejecuta un archivo directamente, __name__ se establece en "__main__". Cuando el mismo archivo se importa como un módulo, __name__ es el nombre del módulo. Este patrón te permite escribir código que se ejecuta cuando ejecutas el archivo directamente pero se omite cuando el archivo es importado por otro módulo.

if __name__ == "__main__": es la guardia estándar para código de módulo ejecutable. Te permite que un módulo sea tanto importable (exponiendo sus funciones) como directamente ejecutable (con código de prueba o demostración). Sin él, importar el módulo ejecutaría cualquier código de nivel superior, que es casi nunca deseado.

Python establece __name__ en "__main__" para el archivo que realmente ejecutaste y en el nombre del módulo propio para cualquier cosa importada. La guardia existe para evitar efectos secundarios de importación (código que hace algo cuando el módulo se carga, como análisis de argumentos, apertura de una conexión, o ejecución de una demostración) de ocurrir en el momento en que otro archivo te importa. El patrón que se sostiene: pon el trabajo real en una función main() y llámala bajo la guardia, así que los importadores obtienen las funciones sin el inicio, y el archivo todavía se ejecuta limpiamente por su cuenta. Omitir la guardia es la razón clásica por la que una suite de pruebas misteriosamente ejecuta el código de demostración de alguien en el instante en que importa el módulo.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

if __name__ == "__main__":
    # esto solo se ejecuta cuando haces: python utils.py
    # no cuando haces: import utils
    print(clamp(150, 0, 100))   # 100

Este es un patrón estándar para cualquier módulo que también es útil como script independiente.

Juno__name__ == '__main__'if __name__ == "__main__": permite que un archivo sea tanto un módulo importable como un script ejecutable. El código bajo la guardia se ejecuta cuando haces python utils.py, y se omite cuando otro archivo hace import utils. Práctico para esconder una demostración o prueba rápida al final de un módulo.
Juno__name__ == '__main__'__name__ es "__main__" solo en el archivo que ejecutaste directamente. La guardia evita que tu código de demostración o prueba se ejecute cuando el módulo se importa, que de otra manera sucede silenciosamente. Es la forma estándar de hacer que un archivo funcione como tanto una biblioteca como un script.
Juno__name__ == '__main__' La guardia detiene los efectos secundarios de importación, análisis de argumentos, demostraciones, conexiones, de ocurrir cuando alguien te importa. Pon el trabajo en main() y llámalo bajo la guardia: los importadores obtienen las funciones, el archivo todavía se ejecuta solo. Omítelo y observa una suite de pruebas ejecutar tu demostración en el instante en que importa el módulo.

Aspectos destacados de la biblioteca estándar

Algunos módulos más que vale la pena conocer. Cada uno resuelve un problema común que llevaría un trabajo significativo implementar por ti mismo.

La biblioteca estándar es extensa; los aspectos destacados a continuación son los que encontrarás más a menudo en código de producción. Para una referencia completa, docs.python.org/3/library es la fuente autorizada.

La biblioteca estándar es un conjunto curado de módulos bien probados y documentados. Antes de recurrir a un paquete de terceros, verifica si la biblioteca estándar tiene una solución: functools, itertools, contextlib, dataclasses, typing, y abc cada uno proporciona herramientas que los paquetes de terceros a menudo reinventan.

collections: tipos de contenedor especializados:

python
from collections import Counter, defaultdict, deque

Counter(["a", "b", "a", "c", "a"])   # Counter({'a': 3, 'b': 1, 'c': 1})
defaultdict(list)                      # dict que auto-crea claves faltantes
deque([1, 2, 3], maxlen=5)            # append/pop rápido de ambos extremos

itertools: herramientas para trabajar con iterables:

python
import itertools

list(itertools.chain([1, 2], [3, 4]))          # [1, 2, 3, 4]
list(itertools.islice(range(100), 5))          # [0, 1, 2, 3, 4]
list(itertools.combinations([1, 2, 3], 2))     # [(1, 2), (1, 3), (2, 3)]
list(itertools.product([0, 1], repeat=2))      # [(0,0), (0,1), (1,0), (1,1)]

sys: acceso al intérprete de Python:

python
import sys

sys.argv        # lista de argumentos de línea de comandos
sys.exit(1)     # salir con un código de estado
sys.version     # cadena de versión de Python

Paquetes de terceros: más allá de la biblioteca estándar, pip instala paquetes de la comunidad:

bash
pip install requests    # biblioteca HTTP
pip install pandas      # manipulación de datos
pip install numpy       # computación numérica

Los paquetes de terceros están fuera del alcance de esta guía, pero el patrón es siempre el mismo: pip install, luego import.

En la práctica

Combinando random, string, y datetime para generar IDs de juego únicos con marcas de tiempo:

python
import random
import string
from datetime import datetime

def generate_game_id(length: int = 8) -> str:
    chars = string.ascii_uppercase + string.digits
    return "".join(random.choices(chars, k=length))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

game_id = generate_game_id()
print(f"[{timestamp()}] Iniciando juego {game_id}")

scores = [random.randint(50, 100) for _ in range(5)]
print(f"Puntuaciones de la ronda: {scores}")
print(f"Mejor: {max(scores)}")

Usando pathlib y datetime para encontrar archivos en un directorio e informar sus tamaños:

python
from pathlib import Path
from datetime import datetime

def find_files(directory: str, pattern: str = "*.csv") -> list[Path]:
    return sorted(Path(directory).glob(pattern))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

files = find_files(".", "*.md")[:3]
print(f"[{timestamp()}] Se encontraron {len(files)} archivo(s)")
for f in files:
    size = f.stat().st_size if f.exists() else 0
    print(f"  {f.name} ({size} bytes)")

Leyendo configuración de la aplicación desde variables de entorno con valores por defecto tipados, y escribiendo entradas de registro de acceso estructuradas como JSON delimitado por saltos de línea:

python
import os
import json
from datetime import datetime
from pathlib import Path

def load_env_config() -> dict:
    return {
        "debug": os.environ.get("DEBUG", "false").lower() == "true",
        "port": int(os.environ.get("PORT", "8080")),
        "log_level": os.environ.get("LOG_LEVEL", "INFO"),
    }

def write_access_log(method: str, path: str, status: int) -> None:
    log_dir = Path("logs")
    log_dir.mkdir(exist_ok=True)
    entry = {
        "ts": datetime.now().isoformat(),
        "method": method,
        "path": path,
        "status": status,
    }
    with open(log_dir / "access.jsonl", "a") as f:
        f.write(json.dumps(entry) + "\n")

config = load_env_config()
print(f"Iniciando en puerto {config['port']}, debug={config['debug']}")
write_access_log("GET", "/users", 200)

JSON delimitado por saltos de línea (.jsonl) es un formato de registro común: cada línea es un objeto JSON válido, lo que hace que sea sencillo transmitir, añadir, y analizar línea por línea sin cargar todo el archivo.