Módulos y paquetes en Python — teoría con utilidades de notas, import y __main__ (y el programa que imprimía cosas que yo no había pedido)
Había escrito un archivo notas.py con mis funciones y, al final, unas líneas para probarlas mientras lo escribía. Todo bien. Días después, en otro archivo, escribí import notas para reutilizar la función media, ejecuté mi programa nuevo y esto fue lo que salió:
Probando media... 7.0 Mi programa empieza aquí 7.0
«Yo solo había pedido la media de 5, 7 y 9… ¿de dónde sale ese «Probando media…» de antes de empezar?» Estuve un rato mirando mi programa nuevo, que no tenía ni rastro de esas líneas. Lo que pasa es que import ejecuta el archivo entero, y yo había dejado mis pruebas sueltas al final del módulo.
Hoy lo ordenamos todo. Verás dónde conviene escribir tus funciones, qué es un módulo, cómo se usa con import y con from … import, por qué existe el prefijo (los espacios de nombres), qué significa de verdad if __name__ == "__main__": y cómo se agrupan varios módulos en un paquete. Todo con un mismo ejemplo: unas utilidades para trabajar con notas. Si vienes de funciones en Python, esto es el siguiente paso natural: ya sabes escribir funciones; ahora aprenderás a guardarlas donde puedan reutilizarse. Y cuando termines, la práctica de módulos y paquetes te espera con tres programas completos.
Tabla de Contenidos
¿Dónde escribo mis funciones?
Hasta ahora has escrito las funciones en el mismo archivo que el programa que las usa. Para programas pequeños es lo normal y funciona perfectamente:
NOTA_MAX = 10
APROBADO = 5.0
def media(a, b, c):
"""Media de tres notas."""
return (a + b + c) / 3
def esta_aprobado(nota):
return nota >= APROBADO
m = media(6.0, 7.5, 7.5)
print("Media:", m)
print("¿Aprobado?", esta_aprobado(m))
Media: 7.0 ¿Aprobado? True
El problema llega cuando el programa crece. Imagina que mañana necesitas media en otro programa distinto: tendrías que copiar y pegar la función. Y si luego corriges un fallo en una copia, la otra sigue teniéndolo. Copiar código es la forma más rápida de tener el mismo error en dos sitios.
Python te da otra opción: escribir las funciones en otro archivo y pedirle a tu programa que las use. Eso es la programación modular: separar un programa en partes independientes, cada una con su funcionalidad, que puedes reutilizar e intercambiar. En Python, esas partes se llaman módulos.
Un módulo es un archivo .py
Un módulo es simplemente un archivo .py, y su nombre es el del archivo sin la extensión. Si guardamos las funciones y las constantes en notas.py…
notas.py
NOTA_MAX = 10
APROBADO = 5.0
def media(a, b, c):
"""Media de tres notas."""
return (a + b + c) / 3
def esta_aprobado(nota):
return nota >= APROBADO
…cualquier otro archivo de la misma carpeta puede usarlas con import:
programa.py
import notas
m = notas.media(6.0, 7.5, 7.5)
print("Media:", m)
print("¿Aprobado?", notas.esta_aprobado(m))
print("Nota máxima posible:", notas.NOTA_MAX)
Media: 7.0 ¿Aprobado? True Nota máxima posible: 10
Entre líneas:
import notasbusca un archivo llamadonotas.pyy lo carga. Escribimosnotas, sin.py.- Para usar algo del módulo, lo escribes con el prefijo:
notas.media(...),notas.NOTA_MAX. Sirve para funciones y también para variables y constantes. programa.pyno contiene ni una línea demedia, y sin embargo la usa. Si mañana mejorasmediaennotas.py, todos los programas que la importan se benefician.
Olvidar el prefijo. Con import notas, el nombre media por sí solo no existe en tu programa: existe notas.media.
Traceback (most recent call last):
File "error_prefijo.py", line 3, in <module>
m = media(6.0, 7.5, 7.5)
^^^^^
NameError: name 'media' is not defined
Ponerle a tu módulo el nombre de uno que Python ya trae (math.py, random.py, string.py…). Cuando hagas import math en ese mismo proyecto, Python puede cargar tu archivo en lugar del suyo, y tendrás errores muy raros. Elige nombres propios y que describan lo que hace el módulo.
Espacios de nombres: por qué hace falta el prefijo
¿Por qué no me deja usar media a secas? Porque cada módulo es un espacio de nombres: una caja con sus propios nombres. Lo que se define dentro de notas.py vive en la caja notas, y por eso necesitas decir de qué caja lo sacas.
Esto sirve para que dos módulos puedan tener cosas con el mismo nombre sin molestarse. Imagina que, además de notas.py, tenemos asistencia.py, que también tiene una función llamada media, pero para calcular la asistencia media en porcentaje:
notas.py
def media(a, b, c):
"""Media de tres notas."""
return (a + b + c) / 3
asistencia.py
def media(p1, p2):
"""Asistencia media (en %) de dos trimestres, redondeada."""
return round((p1 + p2) / 2)
Las dos se llaman media, pero no hay conflicto porque viven en cajas distintas:
import notas
import asistencia
print("Media de notas:", notas.media(6.0, 7.5, 7.5))
print("Asistencia media:", asistencia.media(100, 70), "%")
Media de notas: 7.0 Asistencia media: 85 %
notas.media y asistencia.media son funciones distintas que casualmente comparten nombre. Gracias al prefijo, sabes siempre cuál estás llamando.
from … import y los alias
Escribir notas. delante cada vez puede resultar pesado. Python ofrece dos atajos:
from notas import media, esta_aprobado # nombres sueltos, sin prefijo
import notas as nt # alias corto para el módulo
from notas import NOTA_MAX as MAXIMA # alias para un solo nombre
m = media(6.0, 7.5, 7.5)
print("Media:", m)
print("¿Aprobado?", esta_aprobado(m))
print("Nota máxima:", nt.NOTA_MAX, MAXIMA)
print("¿La misma función?", media is nt.media)
Media: 7.0 ¿Aprobado? True Nota máxima: 10 10 ¿La misma función? True
| Forma | Cómo se usa después | Cuándo conviene |
|---|---|---|
import notas | notas.media(...) | Por defecto: se ve de dónde viene cada cosa |
from notas import media | media(...) | Cuando usas poco el módulo y ningún nombre se va a repetir |
import notas as nt | nt.media(...) | Módulos con nombre largo |
from notas import media as media_notas | media_notas(...) | Dos módulos con un nombre repetido |
Fíjate en la última línea de la salida: media is nt.media da True. Con from … import no copias la función: le pones otra etiqueta a la misma función. Los alias funcionan igual.
Hacer from … import de dos módulos que tienen un nombre repetido. Con from, el nombre entra directamente en tu programa sin prefijo, así que el segundo import pisa al primero:
pisado.py
from notas import media from asistencia import media # ¡pisa a la anterior! print(media(6.0, 7.5, 7.5))
Traceback (most recent call last):
File "pisado.py", line 4, in <module>
print(media(6.0, 7.5, 7.5))
^^^^^^^^^^^^^^^^^^^^
TypeError: media() takes 2 positional arguments but 3 were given
Aquí media ya no es la de notas, sino la de asistencia (la segunda importada), que recibe dos parámetros en vez de tres. Y lo peor: si ambas hubieran recibido el mismo número de parámetros, no habría error: simplemente saldrían resultados equivocados.
Cuando dos módulos comparten un nombre, usa alias (from asistencia import media as media_asistencia) o importa el módulo entero y usa el prefijo. Y evita from modulo import *: mete todos los nombres del módulo de golpe y no sabrás de dónde viene cada uno.
El módulo principal y el código suelto
Volvamos al misterio del principio. Cuando Python importa un módulo, ejecuta el archivo de arriba abajo, la primera vez que se importa. Definir funciones y constantes es inofensivo, pero cualquier instrucción suelta (un print, una prueba, una llamada) también se ejecuta. Mi notas.py de aquella historia era así:
notas.py (con pruebas sueltas)
NOTA_MAX = 10
def media(a, b, c):
return (a + b + c) / 3
# Prueba rápida mientras escribía el módulo
print("Probando media...")
print(media(6.0, 7.5, 7.5))
Lo que necesitamos es que esas pruebas se ejecuten cuando **yo lance notas.py directamente**, pero no cuando otro programa lo importe. Python lo permite porque cada módulo tiene una variable especial, __name__:
- Si lanzas el archivo directamente (
python3 notas.py), Python le pone__name__igual a"__main__": es el módulo principal, el que arranca todo. - Si el archivo se carga con
import,__name__vale el nombre del módulo ("notas").
Así que basta con meter las pruebas dentro de if __name__ == "__main__":. Para verlo, el módulo de abajo también imprime __name__ en cada caso:
notas.py (corregido)
NOTA_MAX = 10
print("__name__ vale:", __name__)
def media(a, b, c):
return (a + b + c) / 3
if __name__ == "__main__":
# Solo se ejecuta si lanzas ESTE archivo directamente
print("Probando media...")
print(media(6.0, 7.5, 7.5))
Si lo importamos desde programa.py (el mismo de antes), las pruebas ya no se cuelan:
__name__ vale: notas Mi programa empieza aquí 7.0
Y si lanzamos notas.py directamente, las pruebas sí se ejecutan:
__name__ vale: __main__ Probando media... 7.0
| Cómo se ejecuta | Valor de __name__ | ¿Entra en el if? |
|---|---|---|
python3 notas.py | "__main__" | Sí |
import notas desde otro archivo | "notas" | No |
Un módulo se ejecuta solo la primera vez que se importa. Si lo importas desde tres archivos, el código suelto de su nivel superior se ejecutaría una sola vez. Pero lo correcto es no depender de eso: deja en el nivel superior solo definiciones, y lo demás, bajo if __name__ == "__main__":.
Paquetes: carpetas de módulos
Cuando tienes muchos módulos, conviene agruparlos. Un paquete es una carpeta con módulos dentro y un archivo especial llamado __init__.py (puede estar vacío). Es lo que le dice a Python: «esta carpeta es un paquete». Los paquetes pueden contener a su vez otros paquetes, los subpaquetes.
Nuestras utilidades crecen y hacemos un paquete universidad, con un subpaquete becas:
programa.py
universidad/
├── __init__.py
├── notas.py
├── asistencia.py
└── becas/
├── __init__.py
└── calculo.py
El punto en el import se lee como una barra de carpetas: universidad.becas.calculo es el archivo universidad/becas/calculo.py. Estos son los dos módulos nuevos (notas.py y asistencia.py son los de antes):
universidad/becas/calculo.py
from universidad.notas import APROBADO
def puntuacion(nota, asistencia):
"""Puntuación de beca: 80 % nota y 20 % asistencia (sobre 10)."""
if nota < APROBADO:
return 0
return round(0.8 * nota + 0.2 * asistencia / 10, 2)
Fíjate en que calculo.py importa algo de otro módulo del paquete con la ruta completa: from universidad.notas import APROBADO. Y este es el programa que lo usa todo:
programa.py
from universidad.notas import media
import universidad.asistencia
from universidad.becas.calculo import puntuacion
m = media(6.0, 7.5, 7.5)
a = universidad.asistencia.media(100, 70)
print("Media:", m, "| Asistencia:", a, "%")
print("Puntuación de beca:", puntuacion(m, a))
Media: 7.0 | Asistencia: 85 % Puntuación de beca: 7.3
Mira las tres formas de importar:
from universidad.notas import media: trae la función suelta; la usamos sin prefijo.import universidad.asistencia: trae el módulo; hay que escribir la ruta completa (universidad.asistencia.media) cada vez.from universidad.becas.calculo import puntuacion: baja dos niveles (paquete, subpaquete, módulo) y coge una sola función.
En las versiones modernas de Python una carpeta también funciona como paquete sin __init__.py (se llaman paquetes de espacio de nombres). Pero la asignatura lo pide, y es lo recomendable: deja claro que la carpeta es un paquete y es el sitio donde, más adelante, puedes poner código de inicialización.
Equivocarte en la ruta o el nombre. Python no adivina: si el módulo no existe en esa ruta, protesta.
Traceback (most recent call last):
File "error_ruta.py", line 1, in <module>
import universidad.nota
ModuleNotFoundError: No module named 'universidad.nota'
Lanzar directamente un archivo que está dentro del paquete. Python busca los paquetes a partir de la carpeta del archivo que ejecutas, y desde dentro de becas/ ya no ve la carpeta universidad. Lanza siempre el programa desde fuera del paquete (programa.py), no los módulos de su interior.
Traceback (most recent call last):
File "universidad/becas/calculo.py", line 1, in <module>
from universidad.notas import APROBADO
ModuleNotFoundError: No module named 'universidad'
Míralo paso a paso en Python Tutor
Python Tutor ejecuta tu programa línea a línea y dibuja las variables y las funciones en memoria. Elige Python 3. Tiene una limitación: solo admite un archivo, así que no puede importar tus propios módulos. Pero podemos simular lo importante: que un nombre es una etiqueta que apunta a una función, y que from … import solo cambia a qué función apunta.
def media(a, b, c):
return (a + b + c) / 3
def media_asistencia(p1, p2):
return round((p1 + p2) / 2)
media_notas = media # alias: otra etiqueta para la misma función
print(media_notas(6.0, 7.5, 7.5))
media = media_asistencia # ahora "media" apunta a la otra función
print(media(100, 70))
print(media_notas(6.0, 7.5, 7.5)) # el alias sigue apuntando a la original
if __name__ == "__main__":
print("Soy el programa principal")
Salida esperada:
7.0 85 7.0 Soy el programa principal
Qué observar, paso a paso:
- Después de definir las dos funciones, en la zona de variables globales aparecen
mediaymedia_asistencia, cada una con una flecha hacia su propia caja de función. - En
media_notas = mediano se crea ninguna función nueva: aparece una segunda flecha hacia la misma caja. Es lo que hace un alias o unfrom … import … as …. - En
media = media_asistencia, la flecha demediasalta a la otra caja. Es lo que hace el segundofrom … import mediadel error de antes: el nombre se queda con la última. - La última llamada, con
media_notas, sigue dando7.0: el alias conservó su flecha original. Por eso los alias son la salida a los conflictos de nombres. - En el bloque final, Python Tutor ejecuta tu código como programa principal, así que la condición
__name__ == "__main__"se cumple y entras en elif.
Resumen
| Concepto | Lo esencial |
|---|---|
| Módulo | Un archivo .py; su nombre es el del archivo sin la extensión |
import modulo | Se usa con prefijo: modulo.funcion() |
from modulo import f | Se usa sin prefijo; cuidado con los nombres repetidos |
Alias (as) | Otra etiqueta para el módulo o la función |
| Espacio de nombres | Cada módulo es una caja: a.media y b.media no chocan |
if __name__ == "__main__": | Código que solo corre al lanzar el archivo directamente |
| Paquete | Carpeta con módulos y un __init__.py; se importa con puntos |
Tres ideas para quedarte:
- Importar ejecuta el archivo. Deja solo definiciones al nivel superior y lo demás bajo
if __name__ == "__main__":. - El prefijo es una ventaja, no un incordio. Dice de dónde viene cada cosa y evita choques de nombres. Si lo quitas con
from, hazlo sabiendo qué nombres entran. - Un punto es una carpeta.
universidad.becas.calculoesuniversidad/becas/calculo.py, y se ejecuta siempre desde fuera del paquete.
En la práctica de módulos y paquetes montarás un boletín con tu propio módulo, un módulo que se prueba a sí mismo y un paquete con subpaquete. Después tienes los ejercicios para comprobar si lo has pillado. Y en el siguiente tema entramos en las secuencias: listas y tuplas.
Siguiente paso: prácticaMódulos y paquetes en Python — práctica con un boletín, un módulo que se prueba solo y un paquete (y el assert que tumbaba mi programa al importar)¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

3 comentarios