Módulo 1 · Lección 41 de 59
Crear tus propios módulos
Cómo repartir tu programa en varios ficheros importables, para qué sirve __name__ y cómo organizarlos en paquetes.
Un programa que crece acaba siendo un fichero de ochocientas líneas en el que hay que desplazarse un minuto para encontrar una función. Llega un momento en que la solución no es escribir mejores funciones, sino repartirlas en varios ficheros.
Y ahí está la buena noticia: no hace falta aprender nada nuevo. Cualquier fichero .py tuyo ya es un módulo importable, exactamente igual que math o random. No hay que declarar nada ni registrarlo en ningún sitio.
Tu primer módulo#
Crea dos ficheros en la misma carpeta. El primero tiene las funciones:
"""Funciones de cálculo que se repiten en toda la tienda."""
IVA_GENERAL = 0.21
def calcular_precio_final(precio, iva=IVA_GENERAL):
"""Devuelve el precio con impuestos incluidos."""
return precio * (1 + iva)
def formatear_euros(cantidad):
"""Devuelve la cantidad como texto con dos decimales y el símbolo."""
return f"{cantidad:.2f} €"El segundo lo usa:
import utilidades
total = utilidades.calcular_precio_final(49.5)
print(utilidades.formatear_euros(total))
print(utilidades.IVA_GENERAL)Ejecuta el segundo desde el terminal, situado en esa carpeta:
python3 tienda.py59.89 €
0.21El nombre del módulo es el del fichero sin la extensión. El fichero se llama utilidades.py, así que se importa como utilidades. Y como es un módulo normal y corriente, admite todas las formas de importación de la lección anterior:
import utilidades # utilidades.calcular_precio_final(...)
from utilidades import calcular_precio_final # calcular_precio_final(...)
from utilidades import calcular_precio_final, IVA_GENERAL # varias cosas
import utilidades as util # util.calcular_precio_final(...)El problema: importar ejecuta el fichero entero#
Importar no es "leer las definiciones". Es ejecutar el fichero de arriba abajo. Las líneas def definen funciones, sí, pero cualquier otra cosa que haya suelta también se ejecuta.
Imagina que durante el desarrollo dejaste una prueba al final de utilidades.py:
IVA_GENERAL = 0.21
def calcular_precio_final(precio, iva=IVA_GENERAL):
return precio * (1 + iva)
print("Probando la función:", calcular_precio_final(100))import utilidades
print("Empieza la tienda")
print(f"{utilidades.calcular_precio_final(49.5):.2f}")python3 tienda.pyProbando la función: 121.0
Empieza la tienda
59.89La prueba se ejecuta al importar, antes incluso de la primera línea de tienda.py. En un módulo pequeño es un mensaje molesto; en uno que pida datos por teclado o borre algo, es un desastre.
Qué es __name__#
Para resolverlo, Python le pone a cada módulo una variable especial llamada __name__, con guiones bajos dobles a los lados. Su valor depende de cómo se esté usando el fichero:
- Si el fichero es el que has lanzado directamente,
__name__vale"__main__". - Si el fichero ha llegado ahí porque otro lo ha importado,
__name__vale el nombre del módulo, es decir, el del fichero sin.py.
Compruébalo. Pon una línea en cada fichero:
print("utilidades.py dice que __name__ vale:", __name__)import utilidades
print("tienda.py dice que __name__ vale:", __name__)python3 tienda.pyutilidades.py dice que __name__ vale: utilidades
tienda.py dice que __name__ vale: __main__El mismo utilidades.py, ejecutado directamente, dice otra cosa:
python3 utilidades.pyutilidades.py dice que __name__ vale: __main__Es decir, __name__ le permite a un fichero saber si es el protagonista o un secundario. "__main__" significa literalmente "yo soy el programa principal".
if __name__ == "__main__":#
Con eso, la solución es una línea de código:
"""Funciones de cálculo que se repiten en toda la tienda."""
IVA_GENERAL = 0.21
def calcular_precio_final(precio, iva=IVA_GENERAL):
"""Devuelve el precio con impuestos incluidos."""
return precio * (1 + iva)
def formatear_euros(cantidad):
"""Devuelve la cantidad como texto con dos decimales y el símbolo."""
return f"{cantidad:.2f} €"
if __name__ == "__main__":
print("Pruebas rápidas de utilidades.py")
print(calcular_precio_final(100))
print(formatear_euros(59.895))Todo lo que esté dentro de ese if se ejecuta solo cuando lanzas el fichero directamente:
python3 utilidades.pyPruebas rápidas de utilidades.py
121.0
59.90 €Y se salta por completo cuando lo importa otro:
from utilidades import calcular_precio_final, formatear_euros
carrito = [19.99, 49.50, 7.25]
for precio in carrito:
print(formatear_euros(calcular_precio_final(precio)))python3 tienda.py24.19 €
59.89 €
8.77 €Organizar en paquetes#
Cuando los módulos empiezan a ser muchos, se agrupan en carpetas. Una carpeta con módulos dentro es un paquete, y se importa con puntos, un punto por cada nivel:
proyecto/
principal.py
tienda/
__init__.py
calculos.py
formatos.pyIVA_GENERAL = 0.21
def calcular_precio_final(precio, iva=IVA_GENERAL):
return precio * (1 + iva)def formatear_euros(cantidad):
return f"{cantidad:.2f} €"from tienda.calculos import calcular_precio_final
from tienda.formatos import formatear_euros
print(formatear_euros(calcular_precio_final(49.5)))python3 principal.py59.89 €tienda.calculos se lee como "el módulo calculos que está dentro del paquete tienda". La estructura de carpetas y la ruta de importación son la misma cosa.
El papel de __init__.py#
Es un fichero, normalmente vacío, que marca la carpeta como paquete. Cumple dos funciones:
- Señalar la intención. Desde Python 3.3 una carpeta sin
__init__.pytambién se puede importar, pero se comporta de otra manera en casos raros. Ponlo: es gratis y evita sorpresas. - Definir la cara pública del paquete. Como se ejecuta al importar el paquete, se usa para subir a primer plano lo más importante y ahorrar rutas largas a quien lo use.
"""Paquete con la lógica de la tienda."""
from tienda.calculos import calcular_precio_finalCon eso, quien use el paquete puede escribir directamente:
import tienda
from tienda.formatos import formatear_euros
print(formatear_euros(tienda.calcular_precio_final(49.5)))59.89 €No llames a tus ficheros como módulos de la biblioteca estándar#
Este es, probablemente, el error más desconcertante de toda la lección, porque el fallo aparece en un sitio que no tiene nada que ver con lo que has tocado.
Recuerda que Python busca primero en la carpeta desde la que ejecutas. Si tienes un random.py tuyo, el tuyo gana, y todo el que pida import random recibirá el tuyo en lugar del de la biblioteca estándar.
numeros_de_la_suerte = [7, 13, 22]import random
print(random.randint(1, 10))python3 sorteo.pyTraceback (most recent call last):
File "/Users/erwin/curso-python/sorteo.py", line 3, in <module>
print(random.randint(1, 10))
^^^^^^^^^^^^^^
AttributeError: module 'random' has no attribute 'randint' (consider renaming '/Users/erwin/curso-python/random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module)