Contenido del capítulo

Guía completa sobre el uso de paquetes en Python. En este capítulo, te voy a enseñar lo que son los paquetes de Python y como funcionan, con ejemplos prácticos y un mini proyecto dividido en 10 ejercicios.

Duración estimada de todo el contenido:
Duración del vídeo:
Contiene 10 ejercicios (mini proyecto) Contiene 1 vídeo.
Contiene 1 mini proyecto.
Tabla de contenidos
Logo

    ¿Cómo se crea un paquete?

    Los paquetes de Python - Programación modular
    Los paquetes de Python - Programación modular

    Cuando creamos aplicaciones relativamente grandes, es normal llegar a tener muchos módulos; muchos de ellos estarán relacionados. Entonces, esos módulos relacionados, pueden formar parte de paquetes, con el fin de crear agrupaciones de módulos.

    Los paquetes en Python, son muy fáciles de crear y permiten generar una estructura jerárquica. Tan solo deberás hacer una carpeta y guardar módulos en ella.

    Paquete de Python con dos módulos

    En la imagen puedes apreciar un ejemplo de paquete. Es una simple carpeta, que contiene archivos de Python.

    En este paquete llamado paquete, tenemos dos módulos, aunque pueden ser todos los que quieras.

    Espacio publicitario

    Con el fin de que empieces a practicar y a ver de qué forma funcionan estos paquetes, crea la estructura planteada en la imagen, y añade el código que te expongo a continuación.

    Para el modulo1.py tienes el siguiente código:

    modulo1.py
    def descripcion():
        print("Soy el módulo 1.")

    Para el modulo2.py tienes el siguiente código:

    modulo2.py
    def descripcion():
        print("Soy el módulo 2.")

    Cuando trabajes con módulos, acuérdate de guardar siempre los cambios, antes de intentar utilizarlos desde otro archivo.

    Si no lo haces, no podrás acceder a los cambios que vayas realizando.

    Para importar modulo1.py en el módulo modulo2.py, solo tienes que utilizar un import:

    import modulo1
    
    def descripcion():
        print("Soy el módulo 2.")
    
    # Llamo a la función del módulo importado
    modulo1.descripcion()
    Resultado en la consola
    Soy el módulo 1.

    Ahora que está claro el acceso de elementos entre módulos de un paquete, vamos a hacer importaciones externas al paquete. Es decir, vamos a importar los módulos del paquete desde un módulo externo.

    Paquete en inglés se traduce como package.

    Espacio publicitario

    Importar los módulos de un paquete

    Para que veas de qué forma funciona el acceso de paquetes y módulos, vamos a crear finalmente la estructura que ves en la imagen:

    Paquetes y módulos en Python

    Por un lado, contamos con la carpeta del proyecto. La que tienes abierta en tu IDE o editor de código.

    Dentro del proyecto nos encontramos con el paquete y un archivo llamado principal.py. Desde este, vamos a intentar acceder a los módulos del paquete.

    Finalmente, dentro del paquete, tenemos los dos módulos.

    Para la prueba de acceso, voy a llamar desde principal.py, a las dos funciones que lleva cada uno de los módulos.

    Este es el código de cada archivo:

    modulo1.py
    def descripcion():
        print("Soy el módulo 1.")
    modulo2.py
    def descripcion():
        print("Soy el módulo 2.")
    principal.py
    import paquete.modulo1, paquete.modulo2
    
    paquete.modulo1.descripcion()
    paquete.modulo2.descripcion()
    Resultado en la consola
    Soy el módulo 1.
    Soy el módulo 2.

    ¡Excelente! Ha funcionado ¿Has visto qué fácil?

    La sintaxis de importación del paquete es esta:

    paquete.modulo

    Es como una ruta a una carpeta, donde tenemos carpeta/archivo.

    Si algo no te funciona, revisa que tengas los nombres del paquete y módulos escritos correctamente, y que cada archivo está en su correspondiente lugar.

    Fíjate bien que los módulos modulo1.py y modulo2.py hayan quedado dentro del paquete, y no en la raíz del proyecto.

    Espacio publicitario

    Importar un solo elemento de un módulo, que pertenece a un paquete

    Lo siguiente que vamos a hacer, será añadir una función más a modulo1.py:

    modulo1.py
    def descripcion():
        print("Soy el módulo 1.")
    
    def suma(a, b):
        return a + b

    Desde principal.py, vamos a importar solo la función suma() de modulo1.py:

    # Importa solo la función suma()
    from paquete.modulo1 import suma
    
    # Utilizamos la función
    operacion = suma(10, 30)
    
    # Imprimimos el resultado
    print(operacion)
    Resultado en la consola
    40

    Paquetes y alias

    En el código visto anteriormente, podemos apreciar que al utilizar paquetes nos queda un acceso demasiado largo a los elementos.

    Por ejemplo:

    principal.py
    import paquete.modulo1, paquete.modulo2
    
    paquete.modulo1.descripcion()
    paquete.modulo2.descripcion()

    Puedes hacer uso de alias para facilitar el acceso:

    principal.py
    import paquete.modulo1 as md1
    import paquete.modulo2 as md2
    
    md1.descripcion()
    md2.descripcion()
    Resultado en la consola
    Soy el módulo 1.
    Soy el módulo 2.

    Espacio publicitario

    Subpaquetes

    Denominamos subpaquetes a aquellos paquetes que están dentro de otros paquetes.

    En la imagen puedes apreciar como el subpaquete, está dentro del paquete.

    Paquetes y subpaquetes con Python

    Para acceder a los módulos del subpaquete, la sintaxis es la siguiente:

    paquete.subpaquete.modulo

    Hagamos una prueba. Crea la estructura que ves en la imagen.

    Crea el subpaquete dentro del paquete y añade los módulos modulo3.py y modulo4.py.

    Añádeles este código:

    modulo3.py
    def descripcion():
        print("Soy el módulo 3.")
    modulo4.py
    def descripcion():
        print("Soy el módulo 4.")

    Ahora, vamos a probar de acceder a cada función, desde el archivo principal.py:

    principal.py
    import paquete.subpaquete.modulo3
    import paquete.subpaquete.modulo4
    
    paquete.subpaquete.modulo3.descripcion()
    paquete.subpaquete.modulo4.descripcion()
    Resultado en la consola
    Soy el módulo 3.
    Soy el módulo 4.

    Una vez más, hemos podido acceder. Sin embargo, las llamadas a los módulos quedan muy largas. Para utilizar un alias con estos subpaquetes, puedes hacerlo así:

    principal.py
    import paquete.subpaquete.modulo3 as md3
    import paquete.subpaquete.modulo4 as md4
    
    md3.descripcion()
    md4.descripcion()

    Espacio publicitario

    Importar un paquete

    En Python, se nos permite importar un paquete entero, en lugar de módulos sueltos, como has visto en los ejemplos anteriores.

    La sintaxis es esta:

    import nombre_paquete

    Sin embargo, si intentas utilizar algo del paquete, no te va a funcionar, así que es bastante inútil.

    Aquí tienes un ejemplo:

    import paquete
    
    paquete.modulo1.descripcion()
    Error en la consola
    AttributeError: module 'paquete' has no attribute 'modulo1'
    Error de atributo: el módulo 'paquete' no tiene el atributo 'modulo1'

    Por el error, podemos ver que está intentando tratar al paquete como si fuera un módulo, y el módulo como un atributo de este. Por eso recibimos el error de atributo inexistente.

    Archivo __init__.py

    Para inicializar paquetes enteros, podemos crear un archivo especial llamado __init__.py. Gracias a este archivo conseguiremos inicializar los datos a nivel de paquete.

    Fíjate en la siguiente imagen. Añade este archivo inicializador al paquete. Sigue con la estructura que llevas hasta ahora, pero añade ese archivo. Solo eso.

    Módulo init de Python para paquetes

    En el archivo __init__.py vamos a escribir el siguiente código:

    __init__.py
    print(f"El paquete llamado '{__name__}' ha sido inicializado.")

    Al ejecutar el archivo principal.py con la importación del paquete, veremos que la inicialización se hace correctamente:

    import paquete
    Resultado en la consola
    El paquete llamado 'paquete' ha sido inicializado.

    Vamos a probar si podemos acceder a elementos a nivel de paquete.

    Añade una sencilla función al archivo __init__.py:

    __init__.py
    print(f"El paquete llamado '{__name__}' ha sido inicializado.")
    
    def descripcion():
        print("Soy el paquete.")

    Lo siguiente, será intentar acceder a esta función desde el archivo principal.py; lo podemos hacer de la siguiente forma:

    principal.py
    import paquete as pqt
    
    pqt.descripcion()
    Resultado en la consola
    El paquete llamado 'paquete' ha sido inicializado.
    Soy el paquete.

    Espacio publicitario

    ¡Bien! Ha funcionado.

    Ahora podrías pensar que poniendo un punto al nombre del paquete, podrás acceder a los diferentes módulos que tiene.

    Probémoslo con el siguiente código en principal.py:

    principal.py
    import paquete as pqt
    
    pqt.modulo1.descripcion()
    Error en la consola
    AttributeError: module 'paquete' has no attribute 'modulo1'
    Error de atributo: el módulo 'paquete' no tiene el atributo 'modulo1'

    No funciona.

    La solución es sencilla. Importa los módulos del paquete en su archivo __init__.py, que le da una inicialización. Así, con la importación lo podremos inicializar todo:

    __init__.py
    import paquete.modulo1, paquete.modulo2
    
    print(f"El paquete llamado '{__name__}' ha sido inicializado.")
    
    def descripcion():
        print("Soy el paquete.")

    Después de guardar los cambios, prueba de ejecutar principal.py:

    import paquete as pqt
    
    pqt.modulo1.descripcion()
    Resultado en la consola
    El paquete llamado 'paquete' ha sido inicializado.
    Soy el módulo 1.

    La variable especial __name__

    Te habrás fijado que en el módulo de inicialización del paquete, en el print(), he utilizado una variable especial llamada __name__. Esta variable establece el valor __main__ si se ejecuta con el archivo que la contiene.

    El término name se traduce como nombre en español. El término main, se traduce como principal.

    Ejecuta el archivo __init__.py con este código (borra todo el código de los ejemplos anteriores y deja solo esto):

    __init__.py
    print(f"El paquete llamado '{__name__}' ha sido inicializado.")
    Resultado en la consola
    El paquete llamado '__main__' ha sido inicializado.

    Si esta variable es ejecutada desde el propio archivo que la contiene, su valor es __main__.

    En cambio, si esta variable se ejecuta desde otro archivo, como hemos hecho anteriormente, nos da el nombre del archivo en donde está escrita:

    Ejecutamos principal.py con este código:

    principal.py
    import paquete
    Resultado en la consola
    El paquete llamado 'paquete' ha sido inicializado.

    Esta vez, en lugar de tomar el nombre __main__, se cambia el valor por el nombre del propio módulo donde se ejecuta.

    En este caso sale paquete, porque estamos trabajando con el espacio de nombres del paquete.

    Espacio publicitario

    Si aplicamos esta variable en un módulo del paquete, sí que nos saldrá el nombre del módulo.

    Por ejemplo:

    principal.py
    import paquete.modulo1
    modulo1.py
    print(f"Módulo '{__name__}'.")

    Si ejecutamos modulo1.py:

    Resultado en la consola
    Módulo '__main__'.

    Si ejecutamos principal.py:

    Resultado en la consola
    Módulo 'paquete.modulo1'.

    if __name__ == __main__

    Esto que parece un verdadero galimatías, en realidad es una cosa muy fácil de entender, pero que a la mayoría se le resiste al dar sus primeros pasos con Python. El porqué se resiste, es por la falta de contexto cuando se quiere saber para qué sirve.

    Con todo lo que te he enseñado, no necesito más que un párrafo para explicártelo.

    Podemos utilizar la variable __name__ para controlar la ejecución del código. Por ejemplo, podemos utilizarla para ejecutar código solo si el módulo se está ejecutando desde el mismo archivo que lo contiene.

    Ejecutamos el archivo __init__.py que contiene lo siguiente:

    __init__.py
    print(f"Código de {__name__} fuera de la zona restringida.")
    
    if __name__ == "__main__":
        print("Ejecutado desde mi propio archivo.")
    Resultado en la consola
    Código de __main__ fuera de la zona restringida.
    Ejecutado desde mi propio archivo.

    Desde este mismo archivo, se ha ejecutado tanto el código de fuera del condicional, como el de dentro, ya que el valor de la variable __name__ es igual a "__main__", si está ejecutándose desde este archivo.

    Espacio publicitario

    Sin embargo, si ejecutamos la importación que tenemos en el archivo principal.py, veremos que el condicional y su código no se ejecutan, al no tener el valor literal "__main__" en la variable __name__, sino que tiene el nombre del paquete:

    principal.py
    import paquete
    Resultado en la consola
    Código de paquete fuera de la zona restringida.

    Gracias a esta técnica tan típica en Python, podemos controlar la forma en que se van a ejecutar las aplicaciones hechas con módulos y paquetes.

    Además, a este if, le podríamos añadir opcionalmente un else, que avise de que no se ejecutó cierto código:

    __init__.py
    print(f"Código de {__name__} fuera de la zona restringida.")
    
    if __name__ == "__main__":
        print("Ejecutado desde mi propio archivo.")
    else:
        print("No se ejecutó el código restringido.")
    principal.py
    import paquete
    Resultado en la consola
    Código de paquete fuera de la zona restringida.
    No se ejecutó el código restringido.

    El uso de __name__ no está restringido a un módulo inicializador como es __init__.py. Puedes utilizar esta técnica en cada módulo de tus programas. Sin ningún problema.

    Todo depende del tipo de soluciones que quieras implementar, y de qué forma las quieras implementar.

    Importaciones de paquetes con *

    Para finalizar con el tema de los paquetes, probemos de hacer una importación de todos los módulos del paquete con el asterisco (*).

    Gracias a esto, podremos disponer de todos los módulos en el espacio de nombres donde hagamos la importación, sin tener que estar poniendo el nombre del paquete cada vez.

    Ahora mismo, al intentar utilizar modulo1, importando con el asterisco, vemos que no está funcionando:

    principal.py
    from paquete import *
    
    modulo1.descripcion()
    Error en la consola
    NameError: name 'modulo1' is not defined
    Error de nombre: el nombre 'modulo1' no está definido

    Para comprobar el acceso a los nombres de paquete, podemos mirar si se ha cargado el nombre modulo1 y modulo2 en el espacio de nombres, con la función predefinida dir():

    principal.py
    from paquete import *
    
    print(dir())
    Resultado en la consola
    ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']

    Vemos que no. Esto es porque hay que utilizar en el módulo __init__.py, una variable especial llamada __all__.

    Espacio publicitario

    En ella, guardaremos una lista con todos los módulos del paquete:

    __init__.py
    __all__ = [
            'modulo1',
            'modulo2'
    ]

    Básicamente, le damos una lista de importaciones en el estado inicial, para poder utilizar la importación con el asterisco.

    Ahora, si llamamos de nuevo a la función dir(), en el archivo principal.py, veremos que las importaciones están cargadas en el espacio de nombres:

    principal.py
    from paquete import *
    
    print(dir())
    Resultado en la consola
    ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'modulo1', 'modulo2']

    El término all se traduce al español como “todo”.

    Entonces, puedes utilizar una función, de por ejemplo, el modulo1, solo nombrándolo, aunque no lo hayamos importado explícitamente en este archivo.

    __init__.py
    __all__ = [
            'modulo1',
            'modulo2'
            ]
    paquete.modulo1.py
    def descripcion():
        print("Soy el módulo 1.")
    principal.py
    from paquete import *
    
    modulo1.descripcion()
    Resultado en la consola
    Soy el módulo 1.

    Espacio publicitario




    Espacio publicitario


    Ejercicios de Python para resolver

    Mini proyecto - Calculadora modular

    A continuación vas a realizar un pequeño proyecto dividido en varios ejercicios. Se trata de una calculadora de consola hecha con módulos y un paquete.

    51. Crea un paquete con cuatro módulos llamado calculadora.

    Además, crea un quinto módulo en la raíz del proyecto.

    La estructura deberá quedar como puedes ver en la siguiente imagen:

    Calculadora hecha con módulos de Python

    Cada uno de los módulos del paquete calculadora, servirán para llevar la lógica de los diferentes cálculos de la calculadora.

    En cambio, el módulo principal, servirá de controlador. Este no llevará toda la lógica del programa, sino que la reunirá mediante importaciones.

    Entonces, lo que se pide en este ejercicio es que crees la estructura que ves en la imagen, sin añadir código a los archivos.

    En este ejercicio, solo tenías que crear la estructura solicitada.

    52. En cada módulo de operaciones, crea una sencilla función que no tenga parámetros. Se le pedirá al usuario dos números para operar con input().

    Se hará la operación en una variable, y se mostrará el resultado en un print().

    Algo como esto:

    def sumar():
        numero_1 = . . .
        numero_2 = . . .
        resultado = . . .
        print("El resultado de la suma es:", resultado)

    Completa el código y adáptalo a cada módulo de operaciones.

    En este ejercicio, tenías que completar el código de los cuatro módulos de operaciones:

    suma.py
    def sumar():
        numero_1 = float(input("Introduzca el primer número: "))
        numero_2 = float(input("Introduzca el segundo número: "))
        resultado = numero_1 + numero_2
        print("El resultado de la suma es:", resultado)
    resta.py
    def restar():
        numero_1 = float(input("Introduzca el primer número: "))
        numero_2 = float(input("Introduzca el segundo número: "))
        resultado = numero_1 - numero_2
        print("El resultado de la resta es:", resultado)
    multiplicacion.py
    def multiplicar():
        numero_1 = float(input("Introduzca el primer número: "))
        numero_2 = float(input("Introduzca el segundo número: "))
        resultado = numero_1 * numero_2
        print("El resultado de la multiplicación es:", resultado)
    division.py
    def dividir():
        numero_1 = float(input("Introduzca el primer número: "))
        numero_2 = float(input("Introduzca el segundo número: "))
        resultado = numero_1 / numero_2
        print("El resultado de la división es:", resultado)

    53. Importa los cuatro módulos de calculadora en el módulo principal, con cuatro importaciones, es decir, no importes todo como paquete.

    Estas son las importaciones en principal.py:

    principal.py
    # Importaciones
    import calculadora.suma as suma
    import calculadora.resta as resta
    import calculadora.multiplicacion as multiplicacion
    import calculadora.division as division

    54. Crea en principal.py una función con un menú para las siguientes operaciones:

    • Avisar al usuario de que debe escoger una opción.
    • Suma.
    • Resta.
    • Multiplicación.
    • División.
    • Salir.

    No te compliques con esta función. Tan solo deben ser print() con números de opción. Por ejemplo, la opción de suma irá con el número 1, la resta, será la opción 2, etc.

    Esta función crea un menú, que se imprime completo con solo llamarla:

    principal.py
    # Función para el menú de la calculadora
    def mostrar_menu():
        print("Seleccione la operación que desea realizar:")
        print("1. Suma")
        print("2. Resta")
        print("3. Multiplicación")
        print("4. División")
        print("5. Salir")

    Espacio publicitario

    55. Crea un bucle while True, que lo primero que haga, sea llamar a la función recién creada. Así, cada vez que se repita el bucle, generará el menú de opciones de nuevo.

    Piensa que si lo ejecutas ahora, solo con la llamada, te generará un bucle infinito, al no tener mecanismo de salida.

    Aquí no te tenías que complicar. Solo llamar a la función en un bucle while True:

    principal.py
    # Bucle principal
    while True:
        mostrar_menu()

    56. En el bucle, le permitiremos al usuario, con un input(), elegir una opción del menú. Esto lo haremos después de la llamada a la función del menú.

    Añadimos el input() al bucle:

    principal.py
    # Bucle principal
    while True:
        mostrar_menu()
        opcion = input("Introduzca el número de la operación deseada: ")

    57. Con esa opción introducida por el usuario, evaluaremos con un condicional, cada opción.

    Mediante esa opción, se llamará a la función correspondiente.

    Por ejemplo:

    Opción 1 elegida - Se llama a la función sumar().

    Con un condicional match, se simplifica mucho la legibilidad, cuando creamos menús:

    principal.py
    # Bucle principal
    while True:
        mostrar_menu()
        opcion = input("Introduzca el número de la operación deseada: ")
    
        match opcion:
            case "1":
                suma.sumar()
            case "2":
                resta.restar()
            case "3":
                multiplicacion.multiplicar()
            case "4":
                division.dividir()
    

    58. Si no lo has hecho aún, implementa una forma de salir del bucle, cuando el usuario ponga la opción para salir. Eso hará que el programa finalice.

    58. Añadimos la opción de salida del bucle:

    principal.py
            case "5":
                print("¡Hasta la próxima!")
                break

    59. Para cuando el usuario se equivoque, habría que añadir un bloque else o default (dependiendo que condicional uses), para advertir que el número de opción introducida no es válido.

    Una vez hayas finalizado este ejercicio, comprueba que la calculadora funciona correctamente, ejecutando cada opción del menú, y haciendo una operación de cada tipo.

    Cuando termines de probarlo todo, puedes continuar con el siguiente ejercicio.

    Si quieres, haz una copia del proyecto, para no tocarlo, e intenta añadir más módulos con más tipos de operaciones. Por ejemplo, cálculo de potencia, de raíz cuadrada, etc.

    Añadimos un bloque default (o else si utilizaste if), para avisar de que se ha introducido un número de opción incorrecta:

    principal.py
            case _:
                print("Número de opción no válido")

    Por ejemplo, si pones un 7 de opción, te saltará este bloque.

    60. Borra todas las importaciones anteriores del archivo principal.py, y haz que funcione la calculadora con este import:

    principal.py
    # Importaciones
    from calculadora import *

    Recuerda que para que funcione este tipo de importación, tienes que crear un paquete con los módulos que estás importando.

    ¡Importante! Si usas la extensión Pylint, puede que te salgan falsos errores con los nombres de los módulos en el código. Pylint (al menos en la versión que utilizo, v2023.10.1) no toma en cuenta los nombres que hay en __all__.

    Pylint es una extensión de Visual Studio Code

    Para que nos funcione la importación con import *, hay que crear un paquete. El paquete será la carpeta calculadora con todos sus módulos.

    En la carpeta calculadora, crea un módulo llamado __init__.py, y dentro le pones una lista con la variable especial __all__, y los nombres de los módulos que forman el paquete:

    __init__.py
    __all__ = [
            "suma",
            "resta",
            "multiplicacion",
            "division"
            ]
    

    Te pongo el código que tengo yo, para que lo puedas comparar con el tuyo:

    principal.py
    # Importaciones
    from calculadora import *
    
    # Función para el menú de la calculadora
    def mostrar_menu():
        print("Seleccione la operación que desea realizar:")
        print("1. Suma")
        print("2. Resta")
        print("3. Multiplicación")
        print("4. División")
        print("5. Salir")
    
    # Bucle principal
    while True:
        mostrar_menu()
        opcion = input("Introduzca el número de la operación deseada: ")
    
        match opcion:
            case "1":
                suma.sumar()
            case "2":
                resta.restar()
            case "3":
                multiplicacion.multiplicar()
            case "4":
                division.dividir()
            case "5":
                print("¡Hasta la próxima!")
                break
            case _:
                print("Número de opción no válido")
    Paquete calculadora __init__.py
    __all__ = [
            "suma",
            "resta",
            "multiplicacion",
            "division"
            ]
    suma.py
    def sumar():
        numero_1 = float(input("Introduzca el primer número: "))
        numero_2 = float(input("Introduzca el segundo número: "))
        resultado = numero_1 + numero_2
        print("El resultado de la suma es:", resultado)
    resta.py
    def restar():
        numero_1 = float(input("Introduzca el primer número: "))
        numero_2 = float(input("Introduzca el segundo número: "))
        resultado = numero_1 - numero_2
        print("El resultado de la resta es:", resultado)
    multiplicacion.py
    def multiplicar():
        numero_1 = float(input("Introduzca el primer número: "))
        numero_2 = float(input("Introduzca el segundo número: "))
        resultado = numero_1 * numero_2
        print("El resultado de la multiplicación es:", resultado)
    division.py
    def dividir():
        numero_1 = float(input("Introduzca el primer número: "))
        numero_2 = float(input("Introduzca el segundo número: "))
        resultado = numero_1 / numero_2
        print("El resultado de la división es:", resultado)


    Espacio publicitario