Skip to content

📙 Clase 11 — Módulos e imports

Fase 2 · Funciones y código organizado ⬅️ Volver al índice de clases

🎯 Qué aprendí

  • Partir el código en archivos (módulos) e importarlos: import, from ... import.
  • El guardián if __name__ == "__main__": y para qué sirve de verdad.
  • La estructura de carpetas de una app de escritorio real (la semilla de la Fase 6).

📖 PARTE TEÓRICA

📄 1. Un módulo = un archivo .py

Cuando app.py crece, se vuelve inmanejable. La solución: cada archivo .py es un módulo que puedes importar desde otro. Ya lo haces a diario:

python
import customtkinter as ctk    # módulo de terceros (instalado con pip)
import json                    # módulo de la librería estándar
import calculos                # ¡TU PROPIO archivo calculos.py!

🧩 2. Crear e importar tu propio módulo

calculos.py — pura lógica, cero interfaz:

python
"""Módulo de lógica: NO sabe nada de la interfaz."""
IVA = 0.18

def con_iva(precio):
    return round(precio * (1 + IVA), 2)

def es_email_valido(texto):
    return "@" in texto and "." in texto

main.py — lo consume:

python
import calculos                        # opción A: importar el módulo entero
from calculos import es_email_valido   # opción B: traer solo lo que necesito

print(calculos.con_iva(50))     # 59.0     (con prefijo módulo.)
print(calculos.IVA)             # 0.18     (también expone variables)
print(es_email_valido("ana@x.com"))   # True   (sin prefijo)
FormaCómo se usa luegoCuándo conviene
import modulomodulo.funcion()queda claro de dónde viene cada cosa
from modulo import ff() directousas pocas cosas y muy seguido
import modulo as mm.funcion()nombres largos (customtkinter as ctk)
from modulo import *todo suelto⚠️ evítalo: no sabes qué trajo ni qué pisó

🛡️ 3. if __name__ == "__main__":

Python le pone a cada módulo una variable __name__:

  • Si ejecutas el archivo directo (python calculos.py) → __name__ vale "__main__".
  • Si lo importas desde otro → vale el nombre del módulo ("calculos").

Eso permite tener código que corre solo en ejecución directa (pruebas, demo):

python
# al final de calculos.py
if __name__ == "__main__":
    # Solo corre con: python calculos.py   (NO al importarlo)
    print("Probando el módulo:", con_iva(100))    # Probando el módulo: 118.0

🧪 Tip de entrevista: "¿Qué hace if __name__ == '__main__'?" → Distingue si el archivo se ejecuta directo o se importa. Protege el código de prueba/arranque para que no corra en cada import.

📌 Detalle práctico: los imports se cachean — si importas el mismo módulo dos veces, el archivo se ejecuta solo la primera.


🖥️ EN TU APP DE ESCRITORIO

Este es el paso de "script" a "proyecto". La estructura profesional mínima de una app CTk:

mi_app/
├── main.py        🚪 punto de entrada: crea la ventana y arranca el mainloop
├── ui.py          🎨 widgets, layouts, callbacks (lo ÚNICO que toca pantalla)
├── logica.py      🧠 validaciones y reglas (funciones puras con return)
└── datos.py       🗄️ leer/guardar (archivos hoy, SQLite en la Fase 6)
python
# logica.py — testeable sin ventana
def validar_contacto(nombre, tel):
    if not nombre:
        return False, "El nombre está vacío"
    if not tel.isdigit():
        return False, "El teléfono debe ser numérico"
    return True, ""
python
# main.py
from ui import crear_app

if __name__ == "__main__":     # el guardián SIEMPRE en el punto de entrada
    app = crear_app()
    app.mainloop()

Beneficio inmediato: puedes probar tu lógica sin abrir la ventana:

bash
python -c "from logica import validar_contacto; print(validar_contacto('Ana','999'))"
# (True, '')

💡 Regla de dependencias (memorízala): ui puede importar logica y datos, pero logica y datos JAMÁS importan ui. La lógica no sabe que existe una ventana. En la Fase 6 formalizamos esto como arquitectura MVC.


🗄️ CON BASE DE DATOS (caso de uso)

datos.py será el único archivo que hable con SQLite. Si mañana cambias SQLite por otra BD, solo tocas ese módulo:

python
# datos.py
import sqlite3

def conectar():
    return sqlite3.connect("app.db")

def obtener_contactos(con):
    return con.execute("SELECT nombre, tel FROM contactos").fetchall()

🏋️ EJERCICIOS CON SOLUCIÓN

Ejercicio 1 — Tu primer módulo

Crea textos.py con una función titulo(s) que devuelva el texto capitalizado y sin espacios extra (" hola mundo " → "Hola Mundo"). Impórtala desde otro archivo y úsala.

Ver solución
python
# textos.py
def titulo(s):
    return s.strip().title()

# main.py
from textos import titulo
print(titulo("  hola mundo "))   # Hola Mundo

Ejercicio 2 — El guardián

Agrega a textos.py una prueba que solo corra al ejecutarlo directo. Comprueba las dos ejecuciones (python textos.py y desde el import).

Ver solución
python
# al final de textos.py
if __name__ == "__main__":
    print(titulo("  probando el modulo "))   # solo con: python textos.py

# python textos.py      → imprime "Probando El Modulo"
# python main.py        → NO imprime la prueba (solo lo del main)

Ejercicio 3 — Detecta el error de diseño

¿Qué está mal aquí (aunque "funcione")?

python
# logica.py
import ui
def validar(nombre):
    if not nombre:
        ui.label_error.configure(text="vacío")
Ver solución
python
# La LÓGICA está importando la UI y tocando un widget: dependencia invertida.
# logica.py no debe saber que existe una pantalla. Correcto:
def validar(nombre):
    if not nombre:
        return False, "El nombre está vacío"
    return True, ""
# ...y que ui.py reciba el (ok, msg) y decida cómo mostrarlo.

❓ Preguntas y respuestas (autoevaluación)

1. ¿Qué es un módulo en Python?

Cualquier archivo .py. Se importa con import nombre (sin el .py).

2. ¿Cuándo vale "__main__" la variable __name__?

Cuando el archivo se ejecuta directo (python archivo.py), no cuando se importa.

3. ¿Por qué evitar from modulo import *?

Trae todo sin saber qué: puede pisar nombres tuyos y no se ve de dónde salió cada función.

4. En una app GUI, ¿quién puede importar a quién?

ui importa logica/datos; nunca al revés. La lógica no conoce la pantalla.


📎 Apuntes relacionados

➡️ Siguiente

Clase 12 · Excepciones — que tu app no se caiga: try/except.