Módulos e importaciones
Usar un archivo que escribiste desde otro archivo, qué hace realmente una importación, por qué hace falta `if __name__ == "__main__"`, la biblioteca estándar, y ocultar un módulo detrás de un archivo tuyo.
¿Cómo quieres aprender?
El problema que vamos a resolver
En el capítulo anterior escribimos una función line_total. Ahora un segundo programa necesita lo mismo.
La forma fácil es copiarla. Eso funciona, el primer día.
Luego cambia la tasa de impuesto. Arreglas un archivo y olvidas el otro. Ahora dos programas muestran dos totales distintos para el mismo pedido, y no hay forma de saber cuál es el correcto — porque ambos lo son, según su propio archivo.
El problema no es el esfuerzo de copiar. El problema es que la verdad ahora vive en dos sitios.
import pricing
print(pricing.TAX_RATE)
print(pricing.line_total(15.0, 3))0.15
51.75Un archivo, y tantos programas como quieras usándolo. Cuando la tasa cambia, cambia en un solo lugar.
Este capítulo trata de esa línea — import — y de lo que realmente ocurre detrás de ella.
Al terminar este capítulo podrás
- Importar un archivo que escribiste desde otro archivo
- Decir qué separa
import x,from x import yeimport x as z - Explicar qué ocurre en el momento de una importación, y cuántas veces ocurre
- Explicar por qué se escribe
if __name__ == "__main__": - Traer módulos de la biblioteca estándar
- Saber dónde busca Python, y evitar ocultar un módulo detrás de un archivo tuyo
Requisitos previos: Ámbito y valores por defecto mutables.
Un módulo es solo un archivo
Un archivo llamado pricing.py es un módulo llamado pricing. No hay que declarar nada ni escribir ninguna línea especial — con tener el archivo basta.
import pricing trae el módulo entero, y lo que hay dentro se alcanza con un punto: pricing.TAX_RATE, pricing.line_total(...).
El punto puede parecer escritura de más, pero cumple una función: al leer, indica de dónde viene la cosa. En un archivo grande eso ayuda mucho.
Traer solo lo que necesitas
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)51.75
NameError: name 'TAX_RATE' is not definedfrom ... import ... trae solo el nombre que se pidió, y ese nombre se usa directamente — sin necesidad de punto.
(Antes de la línea del error Python también imprime unas líneas de rastreo; en este capítulo normalmente solo se muestra la última.)
Pero los demás nombres no llegan. TAX_RATE no se pidió, así que no está aquí, aunque esté en el mismo archivo.
¿Cuál usar? En general: from cuando necesitas dos o tres nombres, el módulo entero cuando necesitas muchos. Y una cosa que nunca hay que hacer — from pricing import *. Trae cada nombre en silencio, y después no hay forma de saber de dónde viene line_total.
Cambiar el nombre al importar
import pricing as p
print(p.line_total(15.0, 3))51.75as le da al módulo un nombre más corto dentro de este archivo.
Úsalo cuando el nombre sea de verdad largo, o cuando exista una convención para él. Un nombre de una sola letra por pereza desperdicia la legibilidad que daba el punto — nadie que lea p.line_total(...) puede saber de dónde viene.
Importar significa ejecutar el archivo
Esta es la parte que la mayoría descubre más tarde, normalmente en mal momento.
tools.py:
print("tools is being set up")
def ping():
return "pong"main.py:
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")main starts
tools is being set up
pong
main endsDos cosas que notar.
Importar significa ejecutar el archivo, de arriba abajo. Las líneas def crean funciones, y todo lo demás — como ese print — de verdad ocurre. Pon un print o un input en el nivel superior de un módulo y le caerá a quien lo importe.
El segundo import tools no hizo nada. Un módulo se ejecuta una vez por programa; después Python devuelve el que ya tiene. Así que importar el mismo módulo en diez sitios es seguro y barato.
__name__ y esa línea famosa
Cada módulo tiene dentro un nombre llamado __name__, que Python asigna por su cuenta.
tools.py:
def ping():
return "pong"
print("module name is", __name__)
if __name__ == "__main__":
print("running directly")Ejecutado como python tools.py:
module name is __main__
running directlyPero import tools desde otro archivo:
module name is toolsLa línea running directly no apareció esta vez, porque la condición era falsa.
La regla es sencilla: el archivo que estás ejecutando tiene __name__ igual a "__main__"; los archivos que se importan tienen su propio nombre.
Así que lo que hay dentro de if __name__ == "__main__": solo se ejecuta cuando el archivo se ejecuta directamente, nunca cuando se importa.
El motivo sale de la sección anterior. Importar significa ejecutar el archivo — así que si main() está al final de un archivo, quien lo importe ejecuta también tu programa de golpe. Esa línea cierra la puerta.
La biblioteca estándar
Muchísimos módulos vienen con el propio Python. No hay que instalar nada.
import math
import statistics
print(math.sqrt(144))
print(math.floor(3.7), math.ceil(3.2))
print(statistics.mean([10, 20, 60]))12.0
3 4
30Otro que aparece a menudo:
import random
random.seed(7)
print(random.randint(1, 100))
print(random.choice(["pen", "bag", "ink"]))42
penSin random.seed(7) cada ejecución daría números distintos — que es, al fin y al cabo, el objetivo. Fijar una semilla congela la secuencia, así que con la misma versión de Python la salida de arriba será igual en tu máquina. Para escribir pruebas esto es muy importante: para probar algo aleatorio, primero hay que hacerlo repetible.
Esto no es "aleatorio", es "parece aleatorio".randomproduce números a partir de un cálculo, así que la misma semilla siempre da la misma secuencia. No lo uses para contraseñas ni tokens — para eso existe el módulosecrets.
Dónde busca Python
Para import pricing, Python busca en este orden: primero los módulos integrados en el intérprete, luego la carpeta del script que se está ejecutando, después la biblioteca estándar, y por último los paquetes instalados.
Ese orden tiene una consecuencia, y sorprende a mucha gente.
Hay un archivo llamado random.py en tu carpeta, y escribes:
import random
print(random.choice([1, 2, 3]))AttributeError: module 'random' has no attribute 'choice'Tu archivo estaba antes que el random real, así que es el que se importó — y no tiene choice dentro.
El mensaje confunde, porque dice que random no tiene choice, lo cual parece imposible. Pero habla de tu random.
La regla: nunca pongas a un archivo el nombre de un módulo. random.py, string.py, json.py, csv.py — parecen nombres inocentes, y justo por eso la trampa funciona.
Una excepción, que explica la regla. En muchas instalaciones (por ejemplo el sistema Linux en el que se ejecutaron los ejemplos de este capítulo), si creas un archivomath.pyy hacesimport math, sigues obteniendo el real, no el tuyo, porque ahímathestá integrado en el intérprete, y los módulos integrados van primero en el orden de búsqueda. Qué módulos están integrados depende de cómo se compiló Python. Así que la trampa se activa con algunos nombres y con otros no, lo cual es más difícil de recordar que simplemente renombrar el archivo.
Organizar archivos en carpetas
A medida que se multiplican, los archivos pueden agruparse en carpetas. Pon un archivo __init__.py dentro de la carpeta — vacío sirve — y la carpeta se convierte en un paquete.
shop/
__init__.py
pricing.py
main.pyLa ruta puede escribirse entonces con puntos:
from shop.pricing import line_total
print(line_total(15.0, 3))51.75Por ahora es suficiente. En el resto de este capítulo nos quedamos con archivos sueltos, sin carpetas.
Un ejemplo completo
Tres archivos, con las dependencias yendo en una sola dirección.
pricing.py:
"""Prices and tax. Nothing here prints, and nothing here asks for input."""
TAX_RATE = 0.15
def line_total(price, quantity):
"""One line of an order, tax included."""
return round(price * quantity * (1 + TAX_RATE), 2)
def order_total(lines):
"""`lines` is a list of (name, price, quantity) tuples."""
return round(sum(line_total(price, qty) for _, price, qty in lines), 2)report.py:
"""Turning numbers into lines of text. It imports pricing; pricing imports nothing."""
import pricing
def receipt(lines):
rows = [f"{name:<8} {pricing.line_total(price, qty):>8.2f}"
for name, price, qty in lines]
rows.append("-" * 17)
rows.append(f"{'total':<8} {pricing.order_total(lines):>8.2f}")
return rows
if __name__ == "__main__":
print("report.py has no data of its own to show")main.py:
"""The only file that is meant to be run."""
import statistics
from report import receipt
ORDER = [
("pen", 15.0, 3),
("bag", 850.0, 1),
("ink", 120.0, 2),
]
def main():
for row in receipt(ORDER):
print(row)
print()
quantities = [qty for _, _, qty in ORDER]
print("mean quantity:", statistics.mean(quantities))
if __name__ == "__main__":
main()Ejecutado como python main.py:
pen 51.75
bag 977.50
ink 276.00
-----------------
total 1305.25
mean quantity: 2Cuatro cosas que vale la pena mirar.
Las dependencias van en una sola dirección. main conoce a report, report conoce a pricing, y pricing no conoce a nadie. Eso forma una cadena, y el archivo al final — pricing — se puede probar completamente por sí solo. Si pricing hubiera necesitado importar report de vuelta, ninguno de los dos podría sostenerse solo.
pricing.py no imprime nada ni pide nada. Recibe valores y devuelve valores — las funciones puras del capítulo veintiuno. Como importar significa ejecutar el archivo, esa contención no es cortesía, es un requisito.
report.py también tiene un if __name__ == "__main__":, y no hace nada útil — el archivo no tiene datos propios, así que no tiene nada que mostrar. Podría haberse omitido. Está ahí para que, si alguna vez se ejecuta directamente, al menos diga algo, en lugar de no hacer nada en silencio.
Ningún archivo salvo main.py hace algo por sí mismo. El programa entero tiene exactamente un punto de entrada, y leerlo te dice qué va a pasar.
Cuando falla
ModuleNotFoundError: No module named 'priceing' Una errata, o el archivo está en otra carpeta. Comprueba que esté junto al script que estás ejecutando.
ImportError: cannot import name 'line_totl' from 'pricing' El módulo se encontró, pero el nombre dentro de él no. El mensaje termina con la ruta completa del archivo del módulo entre corchetes, y a menudo sugiere la coincidencia cercana — Did you mean: 'line_total'?
AttributeError: module 'random' has no attribute 'choice' Casi siempre es un archivo tuyo que oculta el módulo real. Busca ese nombre en tu carpeta y renómbralo. Con eso basta — una vez que el archivo .py desaparece, Python no usa la copia vieja que quedó en __pycache__.
ImportError: cannot import name 'rows' from partially initialized module 'table' (most likely due to a circular import) Dos archivos se importan mutuamente, y al menos uno usa from ... import ... para pedir un nombre que todavía no existe en el otro — porque ese archivo aún no ha terminado de ejecutarse de arriba abajo. La solución habitual es mover lo que ambos necesitan a un tercer archivo que no importe a ninguno de los dos.
Algo se imprimió, o pidió una entrada, en cuanto lo importé Hay código en el nivel superior del módulo. Ponlo dentro de una función y llámala desde dentro de if __name__ == "__main__":.
Edité el archivo, pero sigo viendo el comportamiento anterior Vuelve a ejecutar el programa — un programa en marcha no vuelve a leer el módulo. En un cuaderno, reinicia el kernel.
Paso 4 de 6 — Predice
Comprueba lo que has entendido
Se pidió line_total, TAX_RATE no. ¿Qué pasa?
# pricing.py
TAX_RATE = 0.15
def line_total(price, quantity):
return round(price * quantity * (1 + TAX_RATE), 2)
# main.py
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)- ASe imprime `51.75` y luego `None`
- BSe imprimen `51.75` y `0.15`
- CSe imprime `51.75` y luego un `NameError`
- DUn `ImportError`
import tools se escribe dos veces. ¿Qué se imprime?
# tools.py
print("tools is being set up")
def ping():
return "pong"
# main.py
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")- Atools is being set up main starts pong main ends
- Bmain starts pong main ends
- Cmain starts tools is being set up pong main ends
- Dmain starts tools is being set up tools is being set up pong main ends
Se ejecuta python main.py. ¿Qué se imprime?
# tools.py
print("name:", __name__)
if __name__ == "__main__":
print("direct")
# main.py
import tools
print("done")- Aname: tools done
- Bname: main done
- Cname: __main__ direct done
- Dname: tools direct done
Para responder necesitas una cuenta
Inicia sesión para comprobar tus respuestas
Las preguntas están arriba, y resolverlas mentalmente es lo que de verdad importa. Inicia sesión para ver las respuestas, las explicaciones y las pistas de tres niveles.
Tu turno
Crea tres archivos en una misma carpeta.
textutil.py — dos funciones: initials(name), que convierte "rafi ahmed" en "RA", y shorten(text, limit), que corta un texto largo y añade ... al final. Sin ningún print en este archivo.
table.py — importa textutil, y ofrece rows(people), que recibe una lista de nombres y devuelve una lista de líneas con formato.
main.py — importa table, la llama con algunos nombres e imprime. Pon if __name__ == "__main__": en los tres archivos.
Luego, cuatro experimentos, todos dignos de verse con tus propios ojos:
- Añade
print("loaded")al principio detextutil.pyy ejecutapython main.py. ¿Aparece la línea? ¿Antes o después del primerprintdemain.py? - Escribe
import tabledos veces enmain.py. ¿Cuántas veces se imprimeloaded? - Crea un archivo vacío llamado
statistics.pyen la carpeta, luego enmain.pyimportastatisticsy llama astatistics.mean([1, 2]). Lee el mensaje — ¿de quéstatisticsestá hablando? - Añade
from table import rowsal principio detextutil.py, para que los dos archivos se importen mutuamente, luego ejecutapython main.py. La última línea del mensaje empezará así:ImportError: cannot import name 'rows' from partially initialized module 'table'. (Unimport tablenormal no da error: entoncestextutilsolo obtiene un nombre para el módulo a medio construir, yrowsse busca más tarde, cuando se ejecuta una función.from table import rowspide el nombre ahora, mientrastable.pysigue atascado en su primera línea.)
Las dos últimas son equivocaciones que pueden costar horas si el mensaje no resulta familiar, y segundos una vez que las has causado tú mismo.
Paso 6 de 6
Reto — el quiz del capítulo
Diez preguntas de fácil a difícil. Las últimas son difíciles a propósito.
Inicia sesión para hacer el quiz