Paquetes

Concluiremos el curso con algunos detalles para la organización de código en paquetes. Además, discutiremos sobre la instalación de paquetes de terceros y sobre la preparación necesaria para entregarle nuestro código a otros. El tema de empaquetamiento de código essta en constante evolución y es una parte compleja en el desarrollo de Python. Por tanto, esta sección esta enfocada en compartir algunos principios de organización general de código.

9.1 Paquetes

Si está escribiendo un programa más grande, realmente no desea organizarlo como una gran colección de archivos independientes en el nivel superior. Esta sección presenta el concepto de paquete.

9.1.1 Módulos

Cualquier archivo fuente de Python es un módulo.

# foo.py
def grok(a):
    ...
def spam(b):
    ...

Una declaración import carga y ejecuta un módulo.

# program.py
import foo

a = foo.grok(2)
b = foo.spam('Hello')
...

9.1.2 Paquetes vs Módulos

Para colecciones más grandes de código, es común organizar los módulos en un paquete.

# From this
pcost.py
report.py
fileparse.py
# To this
porty/
    __init__.py
    pcost.py
    report.py
    fileparse.py

Elige un nombre y crea un directorio de nivel superior. porty en el ejemplo anterior (claramente elegir este nombre es el primer paso más importante).

Agregue un archivo __init__.py al directorio. Puede estar vacío.

Coloque sus archivos de origen en el directorio.

9.1.3 Usando un paquete

Un paquete sirve como espacio de nombres para las importaciones.

Esto significa que ahora hay importaciones multinivel.

import porty.report
port = porty.report.read_portfolio('port.csv')

Hay otras variaciones de declaraciones de importación.

from porty import report
port = report.read_portfolio('portfolio.csv')

from porty.report import read_portfolio
port = read_portfolio('portfolio.csv')

Dos problemas

Hay dos problemas principales con este enfoque.

  • importa entre archivos en el mismo paquete.
  • Scripts principales colocados dentro del paquete.

Entonces, básicamente todo se rompe. Pero, aparte de eso, funciona.

9.1.4 Problema: Importaciones

Las importaciones entre archivos en el mismo paquete ahora deben incluir el nombre del paquete en la importación . Recuerda la estructura.

porty/
    __init__.py
    pcost.py
    report.py
    fileparse.py

Ejemplo de importación modificado.

# report.py
from porty import fileparse

def read_portfolio(filename):
    return fileparse.parse_csv(...)

Todas las importaciones son absolutas, no relativas.

# report.py
import fileparse    # Se ROMPE. fileparse not found
...

9.1.5 Importaciones relativas

En lugar de usar directamente el nombre del paquete, puede usar . para hacer referencia al paquete actual.

# report.py
from . import fileparse

def read_portfolio(filename):
    return fileparse.parse_csv(...)

Sintaxis:

from . import modname

Esto facilita el cambio de nombre del paquete.

9.1.6 Problema: guiones principales

La ejecución de un submódulo de paquete como un script principal se rompe.

bash $ python porty/pcost.py # BREAKS
...

Motivo: está ejecutando Python en un solo archivo y Python no ve el resto de la estructura del paquete correctamente (sys.path es incorrecto).

Todas las importaciones se rompen. Para solucionar este problema, debe ejecutar su programa de una manera diferente, usando la opción -m.

bash $ python -m porty.pcost # WORKS
...

9.1.7 archivos __init__.py

El propósito principal de estos archivos es unir módulos.

Ejemplo: consolidar funciones

# porty/__init__.py
from .pcost import portfolio_cost
from .report import portfolio_report

Esto hace que los nombres aparezcan en el nivel superior al importar.

from porty import portfolio_cost
portfolio_cost('portfolio.csv')

En lugar de utilizar las importaciones multinivel.

from porty import pcost
pcost.portfolio_cost('portfolio.csv')

9.1.8 Otra solución para scripts

Como se señaló, ahora debe usar -m paqute.modulo para ejecutar scripts dentro de su paquete.

bash % python3 -m porty.pcost portfolio.csv

Hay otra alternativa: escriba un nuevo script de nivel superior.

#!/usr/bin/env python3
# pcost.py
import porty.pcost
import sys
porty.pcost.main(sys.argv)

Este script vive fuera del paquete. Por ejemplo, mirando la estructura del directorio:

pcost.py       # guion de nviel superior
porty/         # directorio del paquete
    __init__.py
    pcost.py
    ...

9.1.9 Estructura de la aplicación

La organización del código y la estructura de archivos es clave para el mantenimiento de una aplicación.

No existe un enfoque de "talla única" para Python. Sin embargo, una estructura que funciona para muchos problemas es algo como esto.

porty-app/
  README.txt
  script.py         # SCRIPT
  porty/
    # codigo de la biblioteca / libreria
    __init__.py
    pcost.py
    report.py
    fileparse.py

El nivel superior porty-app es un contenedor para todo lo demás: documentación, scripts de nivel superior, ejemplos, etc.

Nuevamente, los scripts de nivel superior (si los hay) deben existir fuera del paquete de código. Un nivel más.

#!/usr/bin/env python3
# porty-app/script.py
import sys
import porty

porty.report.main(sys.argv)

9.1.10 Ejercicios

En este punto, tiene un directorio con varios programas:

pcost.py          # computes portfolio cost
report.py         # Makes a report
ticker.py         # Produce a real-time stock ticker

Hay una variedad de módulos de soporte con otras funcionalidades:

stock.py          # Stock class
portfolio.py      # Portfolio class
fileparse.py      # CSV parsing
tableformat.py    # Formatted tables
follow.py         # Follow a log file
typedproperty.py  # Typed class properties

En este ejercicio, vamos a limpiar el código y ponerlo en un paquete común.

Ejercicio 9.1: Hacer un paquete simple

Cree un directorio llamado porty/y coloque todos los archivos de Python anteriores en él. Además, cree un init.pyarchivo vacío y colóquelo en el directorio. Debería tener un directorio de archivos como este:

porty/
    __init__.py
    fileparse.py
    follow.py
    pcost.py
    portfolio.py
    report.py
    stock.py
    tableformat.py
    ticker.py
    typedproperty.py

Elimina el archivo __pycache__ que está en tu directorio. Este contiene módulos de Python precompilados de antes. Queremos empezar de nuevo.

Intente importar algunos de los módulos del paquete:

>>> import porty.report
>>> import porty.pcost
>>> import porty.ticker

Si estas importaciones fallan, vaya al archivo apropiado y corrija las importaciones del módulo para incluir una importación relativa al paquete. Por ejemplo, una declaración como import fileparse podría cambiar a lo siguiente:

# report.py
from . import fileparse
...

Si tiene una declaración como from fileparse import parse_csv, cambie el código a lo siguiente:

# report.py
from .fileparse import parse_csv
...

Ejercicio 9.2: Crear un directorio de aplicaciones

Poner todo su código en un "paquete" no suele ser suficiente para una aplicación. A veces hay archivos de apoyo, documentación, scripts y otras cosas. Estos archivos deben existir FUERA del directorio porty/ que creó anteriormente.

Cree un nuevo directorio llamado porty-app. Mueva el portydirectorio que creó en el ejercicio 9.1 a ese directorio. Copie los archivos de prueba Data/portfolio.csv y Data/prices.csv en este directorio. Además, cree un archivo README.txt con información sobre usted. Su código ahora debería estar organizado de la siguiente manera:

porty-app/
    portfolio.csv
    prices.csv
    README.txt
    porty/
        __init__.py
        fileparse.py
        follow.py
        pcost.py
        portfolio.py
        report.py
        stock.py
        tableformat.py
        ticker.py
        typedproperty.py

Para ejecutar su código, debe asegurarse de estar trabajando en el directorio porty-app/ de nivel superior . Por ejemplo, desde la terminal:

shell % cd porty-app
shell % python3
>>> import porty.report
>>>

Intente ejecutar algunos de sus scripts anteriores como programa principal:

shell % cd porty-app
shell % python3 -m porty.report portfolio.csv prices.csv txt
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100       9.22     -22.98
       IBM         50     106.28      15.18
       CAT        150      35.46     -47.98
      MSFT        200      20.89     -30.34
        GE         95      13.48     -26.89
      MSFT         50      20.89     -44.21
       IBM        100     106.28      35.84

shell %

Ejercicio 9.3: Scripts de nivel superior

Usar el comando python -m suele ser un poco extraño. Es posible que desee escribir un script de nivel superior que simplemente se ocupe de las rarezas de los paquetes. Cree un script print-report.py que produzca el informe anterior:

#!/usr/bin/env python3
# print-report.py
import sys
from porty.report import main
main(sys.argv)

Coloque este script en el directorio porty-app/ de nivel superior. Asegúrese de poder ejecutarlo en esa ubicación:

shell % cd porty-app
shell % python3 print-report.py portfolio.csv prices.csv txt
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100       9.22     -22.98
       IBM         50     106.28      15.18
       CAT        150      35.46     -47.98
      MSFT        200      20.89     -30.34
        GE         95      13.48     -26.89
      MSFT         50      20.89     -44.21
       IBM        100     106.28      35.84

shell %

Su código final ahora debería estar estructurado de esta manera:

porty-app/
    portfolio.csv
    prices.csv
    print-report.py
    README.txt
    porty/
        __init__.py
        fileparse.py
        follow.py
        pcost.py
        portfolio.py
        report.py
        stock.py
        tableformat.py
        ticker.py
        typedproperty.py

9.2 Módulos de terceros

Python tiene una gran biblioteca de módulos integrados (baterías incluidas).

Incluso hay más módulos de terceros. Compruébelos en el índice de paquetes de Python o PyPi. O simplemente haga una búsqueda en Google de un tema específico.

Cómo manejar las dependencias de terceros es un tema en constante evolución con Python. Esta sección simplemente cubre los conceptos básicos para ayudarlo a comprender cómo funciona.

9.2.1 La ruta de búsqueda del módulo

sys.path es un directorio que contiene la lista de todos los directorios controlados por la declaración import. Míralo:

>>> import sys
>>> sys.path
... look at the result ...
>>>

Si importa algo y no está ubicado en uno de esos directorios, obtendrá una excepción ImportError.

9.2.2 Módulos de biblioteca estándar

Los módulos de la biblioteca estándar de Python generalmente provienen de una ubicación como /usr/local/lib/python3.6. Puede averiguarlo con certeza haciendo una prueba breve:

>>> import re
>>> re
<module 're' from '/usr/local/lib/python3.6/re.py'>
>>>

Simplemente mirar un módulo en el REPL es un buen consejo de depuración que debe conocer. Le mostrará la ubicación del archivo.

9.2.3 Módulos de terceros

Los módulos de terceros generalmente se encuentran en un directorio dedicado site-packages. Lo verá si realiza los mismos pasos que el anterior:

>>> import numpy
>>> numpy
<module 'numpy' from '/usr/local/lib/python3.6/site-packages/numpy/__init__.py'>
>>>

Nuevamente, mirar un módulo es un buen consejo de depuración si está tratando de averiguar por qué algo relacionado con import no está funcionando como se esperaba.

9.2.4 Instalación de módulos

La técnica más común para instalar un módulo de terceros es usar pip. Por ejemplo:

bash % python3 -m pip install packagename

Este comando descargará el paquete y lo instalará en el directorio site-packages.

9.2.5 Problemas

  • Es posible que esté utilizando una instalación de Python que no controla directamente.
    • Una instalación aprobada por la empresa
    • Estás usando la versión de Python que viene con el sistema operativo.
  • Es posible que no tenga permiso para instalar paquetes globales en la computadora.
  • Puede haber otras dependencias.

9.2.6 Ambientes virtuales

Una solución común para los problemas de instalación de paquetes es crear un "entorno virtual" para usted. Naturalmente, no hay "una manera" de hacer esto; de hecho, existen varias herramientas y técnicas en competencia. Sin embargo, si está utilizando una instalación estándar de Python, puede intentar escribir esto:

bash % python -m venv mypython
bash %

Después de unos momentos de espera, tendrá un nuevo directorio mypython que es su propia instalación de Python. Dentro de ese directorio encontrará un directorio bin/ (Unix) o un directorio Scripts/ (Windows). Si ejecuta el script activate que se encuentra allí, “activará” esta versión de Python, convirtiéndola en el comando python predeterminado para el shell. Por ejemplo:

bash % source mypython/bin/activate
(mypython) bash %

Desde aquí, ahora puede comenzar a instalar paquetes de Python usted mismo. Por ejemplo:

(mypython) bash % python -m pip install pandas
...

Con el fin de experimentar y probar diferentes paquetes, un entorno virtual normalmente funcionará bien. Si, por otro lado, está creando una aplicación y tiene dependencias de paquetes específicas, ese es un problema ligeramente diferente.

9.2.7 Manejo de dependencias de terceros en su aplicación

Si ha escrito una aplicación y tiene dependencias específicas de terceros, un desafío se refiere a la creación y preservación del entorno que incluye su código y las dependencias. Lamentablemente, esta ha sido un área de gran confusión y cambios frecuentes durante la vida de Python. Continúa evolucionando incluso ahora.

En lugar de proporcionar información que seguramente estará desactualizada pronto, le recomiendo la Guía del usuario de Python Packaging.

9.2.8 Ejercicios

Ejercicio 9.4: Creación de un entorno virtual

Vea si puede recrear los pasos para crear un entorno virtual e instalar pandas en él como se muestra arriba.

9.3 Distribución

En algún momento, es posible que desee dar su código a otra persona, posiblemente solo a un compañero de trabajo. Esta sección brinda la técnica más básica para hacerlo. Para obtener información más detallada, deberá consultar la Guía del usuario de empaquetado de Python.

9.3.1 Creando un archivo setup.py

Agregue un archivo setup.py al nivel superior del directorio de su proyecto.

# setup.py
import setuptools

setuptools.setup(
    name="porty",
    version="0.0.1",
    author="Your Name",
    author_email="you@example.com",
    description="Practical Python Code",
    packages=setuptools.find_packages(),
)

9.3.2 Creando MANIFEST.in

Si hay archivos adicionales asociados con su proyecto, especifíquelos con un MANIFEST.inarchivo. Por ejemplo:

# MANIFEST.in
include *.csv

Coloque el archivo MANIFEST.in en el mismo directorio que setup.py.

9.3.2 Crear una distribución de origen

Para crear una distribución de su código, use el archivo setup.py. Por ejemplo:

bash % python setup.py sdist

Esto creará un archivo .tar.gzo.zip en el directorio dist/. Ese archivo es algo que ahora puede regalar a otros.

9.3.3 Instalando su código

Otros pueden instalar su código Python usando pip de la misma manera que lo hacen con otros paquetes. Simplemente deben proporcionar el archivo creado en el paso anterior. Por ejemplo:

bash % python -m pip install porty-0.0.1.tar.gz

Comentario

Los pasos anteriores describen los conceptos básicos más mínimos para crear un paquete de código Python que puede darle a otra persona. En realidad, puede ser mucho más complicado dependiendo de las dependencias de terceros, si su aplicación incluye o no código externo (es decir, C / C ++), etc. Cubrir eso está fuera del alcance de este curso. Solo hemos dado un pequeño primer paso.

9.3.4 Ejercicios

Ejercicio 9.5: hacer un paquete

Tome el código porty-app/ que creó para el ejercicio 9.3 y vea si puede recrear los pasos descritos aquí. Específicamente, agregue un archivo setup.py y un archivo MANIFEST.in al directorio de nivel superior. Cree un archivo de distribución de origen ejecutando python setup.py sdist.

Como paso final, vea si puede instalar su paquete en un entorno virtual de Python.