Come scrivere i propri pacchetti Python

Panoramica

Python è un meraviglioso linguaggio di programmazione e molto altro. Uno dei suoi punti deboli è la confezione. Questo è un fatto ben noto nella comunità. Installare, importare, usare e creare pacchetti è migliorato nel corso degli anni, ma non è ancora alla pari con i linguaggi più recenti come Go and Rust che potrebbero imparare molto dalle lotte di Python e di altri linguaggi più maturi. 

In questo tutorial imparerai tutto ciò che devi sapere per creare e condividere i tuoi pacchetti. Per informazioni generali sui pacchetti Python, leggi Come usare i pacchetti Python.

Imballaggio di un progetto

Il confezionamento di un progetto è il processo mediante il quale si prende un insieme di moduli Python eventualmente auspicabile e possibilmente altri file e li si inserisce in una struttura che può essere facilmente utilizzata. Ci sono varie cose da considerare, come le dipendenze da altri pacchetti, la struttura interna (sotto-pacchetti), il controllo delle versioni, il pubblico di destinazione e la forma del pacchetto (sorgente e / o binario).

Esempio

Iniziamo con un rapido esempio. Il pacchetto conman è un pacchetto per la gestione della configurazione. Supporta diversi formati di file e configurazioni distribuite usando etcd.

I contenuti di un pacchetto sono in genere memorizzati in una singola directory (sebbene sia comune suddividere sotto-pacchetti in più directory) e talvolta, come in questo caso, nel proprio repository git. 

La directory root contiene vari file di configurazione (setup.py è obbligatorio e il più importante) e il codice del pacchetto stesso è di solito in una sottodirectory il cui nome è il nome del pacchetto e idealmente una directory di test. Ecco come appare "conman":

> albero. ├── LICENZA ├── MANIFEST.in ├── README.md ├── conman │ ├── __init__.py │ ├── __pycache__ │ ├── conman_base.py │ ├── conman_etcd.py │ └── conman_file.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── test-requirements.txt ├── test │ ├── __pycache__ │ ├── conman_etcd_test.py │ ├── conman_file_test .py │ └── etcd_test_util.py └── tox.ini

Diamo una rapida occhiata al setup.py file. Importa due funzioni dal pacchetto setuptools: impostare() e find_packages (). Quindi chiama il impostare() funzione e usi find_packages () per uno dei parametri.

da setuptools import setup, find_packages setup (name = 'conman', version = "0.3", url = "https://github.com/the-gigi/conman", license = "MIT", author = "Gigi Sayfan" , author_email = "[email protected]", description = "Gestisci file di configurazione", packages = find_packages (exclude = ['tests']), long_description = open ('README.md'). read (), zip_safe = False, setup_requires = ['nose> = 1.0'], test_suite = "nose.collector") 

Questo è abbastanza normale. Mentre il setup.py il file è un normale file Python e puoi fare tutto ciò che vuoi, il suo compito principale è chiamare il file impostare() funziona con i parametri appropriati perché verrà invocato da vari strumenti in modo standard durante l'installazione del pacchetto. Troverò i dettagli nella prossima sezione.

I file di configurazione

Inoltre setup.py, ci sono alcuni altri file di configurazione opzionali che possono essere visualizzati qui e servono a vari scopi.

Setup.py

Il impostare() la funzione accetta un numero elevato di argomenti con nome per controllare molti aspetti dell'installazione del pacchetto e l'esecuzione di vari comandi. Molti argomenti specificano i metadati utilizzati per la ricerca e il filtro quando si carica il pacchetto in un repository.

  • nome: il nome del tuo pacchetto (e come sarà elencato su PYPI)
  • versione: questo è fondamentale per mantenere una corretta gestione delle dipendenze
  • url: l'URL del tuo pacchetto, in genere GitHub o forse l'URL readthedocs
  • pacchetti: elenco di sotto-pacchetti che devono essere inclusi; find_packages () aiuta qui
  • setup_requires: qui si specificano le dipendenze
  • test_suite: quale strumento eseguire al momento del test

Il Descrizione lunga è impostato qui per i contenuti del README.md file, che è una buona pratica per avere un'unica fonte di verità.

setup.cfg

Il file setup.py serve anche un'interfaccia della riga di comando per eseguire vari comandi. Ad esempio, per eseguire i test di unità, è possibile digitare: test python setup.py

eseguendo test eseguendo egg_info scrivendo conman.egg-info / PKG-INFO scrivendo nomi di livello superiore a conman.egg-info / top_level.txt scrivendo dependency_links a conman.egg-info / dependency_links.txt leggendo manifest file 'conman.egg-info /SOURCES.txt 'lettura manifest template' MANIFEST.in 'scrivendo file manifest' conman.egg-info / SOURCES.txt 'eseguendo build_ext test_add_bad_key (conman_etcd_test.ConManEtcdTest) ... ok test_add_good_key (conman_etcd_test.ConManEtcdTest) ... ok test_dictionary_access (conman_etcd_test.ConManEtcdTest ) ... ok test_initialization (conman_etcd_test.ConManEtcdTest) ... ok test_refresh (conman_etcd_test.ConManEtcdTest) ... ok test_add_config_file_from_env_var (conman_file_test.ConmanFileTest) ... ok test_add_config_file_simple_guess_file_type (conman_file_test.ConmanFileTest) ... ok test_add_config_file_simple_unknown_wrong_file_type (conman_file_test.ConmanFileTest) ... ok test_add_config_file_simple_with_file_type (conman_file_test.ConmanFileTest) ... ok test_add_config_file_simple_w rong_file_type (conman_file_test.ConmanFileTest) ... ok test_add_config_file_with_base_dir (conman_file_test.ConmanFileTest) ... ok test_dictionary_access (conman_file_test.ConmanFileTest) ... ok test_guess_file_type (conman_file_test.ConmanFileTest) ... test_init_no_files ok (conman_file_test.ConmanFileTest) ... test_init_some_bad_files ok (conman_file_test.ConmanFileTest) ... test_init_some_good_files ok ( conman_file_test.ConmanFileTest) ... ok -------------------------------------------- -------------------------- Esegui 16 test in 0.160s OK 

Setup.cfg è un file ini format che può contenere i valori predefiniti delle opzioni per i comandi a cui si passa setup.py. Qui, setup.cfg contiene alcune opzioni per nosetests (il nostro test runner):

[nosetests] verbose = 1 nocapture = 1 

MANIFEST.in

Questo file contiene file che non fanno parte della directory del pacchetto interno, ma si desidera comunque includerli. Quelli sono in genere il readme file, il file di licenza e simili. Un file importante è il requirements.txt. Questo file viene utilizzato da pip per installare altri pacchetti richiesti.

Qui è Conman MANIFEST.in file:

include LICENZA include README.md include requirements.txt

dipendenze

È possibile specificare le dipendenze sia nel file install_requires sezione di setup.py e in a requirements.txt file. Pip installerà automaticamente le dipendenze da install_requires, ma non dal requirements.txt file. Per installare questi requisiti, devi specificarlo esplicitamente quando esegui pip: pip installa -r requirements.txt.

Il install_requires l'opzione è progettata per specificare requisiti minimi e più astratti a livello di versione principale. Il file requirements.txt è per requisiti più concreti, spesso con versioni secondarie bloccate.

Ecco il file dei requisiti di Conman. Puoi vedere che tutte le versioni sono bloccate, il che significa che può avere un impatto negativo se uno di questi pacchetti si aggiorna e introduce una modifica che rompe il conman.

PyYAML == 3.11 python-etcd == 0.4.3 urllib3 == 1.7 pyOpenSSL == 0.15.1 psutil == 4.0.0 sei == 1.7.3

Appuntare ti dà prevedibilità e tranquillità. Questo è particolarmente importante se molte persone installano il pacchetto in momenti diversi. Senza bloccare, ogni persona otterrà un diverso mix di versioni di dipendenza in base a quando lo ha installato. Il rovescio della medaglia è che se non si tiene il passo con lo sviluppo delle proprie dipendenze, si può rimanere bloccati su una versione vecchia, poco performante e persino vulnerabile di qualche dipendenza.

Originariamente avevo scritto conman nel 2014 e non ho prestato molta attenzione a questo. Ora, per questo tutorial ho aggiornato tutto e ci sono stati alcuni importanti miglioramenti su tutta la linea per quasi tutte le dipendenze.

distribuzioni

È possibile creare una distribuzione di origine o una distribuzione binaria. Coprirò entrambi.

Distribuzione della fonte

Si crea una distribuzione di origine con il comando: python setup.py sdist. Ecco l'output di conman:

> python setup.py sdist esegue sdist esegue egg_info scrive conman.egg -info / PKG-INFO scrive i nomi di livello superiore in conman.egg -info / top_level.txt scrive dependency_links in conman.egg-info / dependency_links.txt sta leggendo il file manifest 'conman.egg-info / SOURCES.txt' lettura manifest template 'MANIFEST.in' scrittura manifest file 'conman.egg-info / SOURCES.txt' avviso: sdist: file standard non trovato: dovrebbe avere uno di README, README. rst, README.txt esecuzione controllo creazione di conman-0.3 creazione di conman-0.3 / conman creazione di conman-0.3 / conman.egg-info creazione di collegamenti complessi in conman-0.3 ... LICENZA hard linking -> conman-0.3 hard linking MANIFEST.in -> conman-0.3 hard linking README.md -> conman-0.3 hard linking requirements.txt -> conman-0.3 hard linking setup.cfg -> conman-0.3 hard linking setup.py -> conman-0.3 hard linking conman / __ init__.py -> conman-0.3 / conman hard linking conman / conman_base.py -> conman-0.3 / conman hard linking conman / conman_etcd.py -> conman-0.3 / conman hard linking conman / conman_fil e.py -> conman-0.3 / conman hard linking conman.egg -info / PKG-INFO -> conman-0.3 / conman.egg-info hard link conman.egg -info / SOURCES.txt -> conman-0.3 / conman .leg-info hard linking conman.egg-info / dependency_links.txt -> conman-0.3 / conman.egg-info hard linking conman.egg-info / not-zip-safe -> conman-0.3 / conman.egg-info hard linking conman.egg -info / top_level.txt -> conman-0.3 / conman.egg-info copying setup.cfg -> conman-0.3 Scrittura conman-0.3 / setup.cfg creando dist Creazione di archivio tar rimuovendo 'conman-0.3' (e tutto sotto di esso) 

Come potete vedere, ho ricevuto un avvertimento riguardo alla mancanza di un file README con uno dei prefissi standard perché mi piace Markdown, quindi ho un "README.md". Oltre a questo, sono stati inclusi tutti i file sorgente del pacchetto e i file aggiuntivi. Quindi, un gruppo di metadati è stato creato nel conman.egg-info directory. Finalmente, un archivio tar compresso chiamato truffatore-0.3.tar.gz viene creato e inserito in a dist sottodirectory.

L'installazione di questo pacchetto richiederà un passo di costruzione (anche se è puro Python). Puoi installarlo usando pip normalmente, semplicemente passando il percorso del pacchetto. Per esempio:

pip install dist / conman-0.3.tar.gz Elaborazione ./dist/conman-0.3.tar.gz Installazione dei pacchetti raccolti: conman Esecuzione setup.py install per conman ... done Installato con successo-0,3

Conman è stato installato nei pacchetti del sito e può essere importato come qualsiasi altro pacchetto:

import conman conman .__ file__ '/Users/gigi/.virtualenvs/conman/lib/python2.7/site-packages/conman/__init__.pyc'

Ruote

Le ruote sono un modo relativamente nuovo di impacchettare il codice Python e facoltativamente le estensioni C. Sostituiscono il formato dell'uovo. Esistono diversi tipi di ruote: ruote in puro pitone, ruote di piattaforma e ruote universali. Le ruote Pure Python sono pacchetti come conman che non hanno alcun codice di estensione C. 

Le ruote della piattaforma hanno il codice di estensione C. Le ruote universali sono ruote Python pure compatibili con Python 2 e Python 3 con lo stesso codice base (non richiedono nemmeno 2to3). Se hai un pacchetto Python puro e vuoi che il tuo pacchetto supporti sia Python 2 che Python 3 (diventando sempre più importante), puoi creare una singola build universale invece di una ruota per Python 2 e una ruota per Python 3. 

Se il tuo pacchetto ha il codice di estensione C, devi costruire una ruota della piattaforma per ogni piattaforma. L'enorme vantaggio delle ruote specialmente per i pacchetti con estensioni C è che non è necessario avere il compilatore e le librerie di supporto disponibili sul computer di destinazione. La ruota contiene già un pacchetto costruito. Quindi sai che non mancherà di costruire ed è molto più veloce da installare perché è letteralmente solo una copia. Le persone che usano librerie scientifiche come Numpy e Pandas possono davvero apprezzarlo, poiché l'installazione di tali pacchetti richiedeva molto tempo e poteva non riuscire se mancava qualche libreria o il compilatore non era configurato correttamente.

Il comando per costruire ruote pure o platform è: python setup.py bdist_wheel.

Setuptools: il motore che fornisce il impostare() funzione: rileverà automaticamente se è necessaria una ruota pura o di piattaforma.

eseguendo bdist_wheel eseguendo build eseguendo build_py creando build creando build / lib creando build / lib / conman copia conman / __ init__.py -> build / lib / conman copia conman / conman_base.py -> build / lib / conman copia conman / conman_etcd.py -> build / lib / conman copy conman / conman_file.py -> build / lib / conman installando su build / bdist.macosx-10.9-x86_64 / wheel con installazione in esecuzione install_lib creando build / bdist.macosx-10.9-x86_64 creando build / bdist.macosx-10.9-x86_64 / wheel che crea build / bdist.macosx-10.9-x86_64 / wheel / conman copia build / lib / conman / __ init__.py -> build / bdist.macosx-10.9-x86_64 / wheel / conman copia build /lib/conman/conman_base.py -> build / bdist.macosx-10.9-x86_64 / wheel / conman copia build / lib / conman / conman_etcd.py -> build / bdist.macosx-10.9-x86_64 / wheel / conman copia build /lib/conman/conman_file.py -> build / bdist.macosx-10.9-x86_64 / wheel / conman in esecuzione install_egg_info in esecuzione egg_info creazione di conman.egg-info scrittura conman.egg-info / PKG-INFO scrittura di nomi di livello superiore es a conman.egg-info / top_level.txt scrivendo dependency_links a conman.egg-info / dependency_links.txt scrivendo il file manifest 'conman.egg-info / SOURCES.txt' leggendo manifest file 'conman.egg-info / SOURCES.txt 'read manifest template' MANIFEST.in 'scrivendo file manifest' conman.egg-info / SOURCES.txt 'Copia conman.egg-info per compilare / bdist.macosx-10.9-x86_64 / wheel / conman-0.3-py2.7. egg-info che esegue install_scripts creando build / bdist.macosx-10.9-x86_64 / wheel / conman-0.3.dist-info / WHEEL

Controllo del dist directory, puoi vedere che è stata creata una ruota Python pura:

ls -la dist dist / totale 32 -rw-r - r-- 1 gigi staff 5.5K Feb 29 07:57 conman-0.3-py2-none-any.whl -rw-r - r-- 1 gigi staff 4.4K Feb 28 23:33 conman-0.3.tar.gz

Il nome "conman-0.3-py2-none-any.whl" ha diversi componenti: nome del pacchetto, versione del pacchetto, versione di Python, versione della piattaforma e infine l'estensione "whl".

Per creare pacchetti universali, basta aggiungere --universale, come in python setup.py bdist_wheel --universal.

La ruota risultante si chiama "conman-0.3-py2.py3-none-any.whl".

Nota che è tua responsabilità assicurarti che il tuo codice funzioni effettivamente con Python 2 e Python 3 se crei un pacchetto universale.

Conclusione

Scrivere i propri pacchetti Python richiede di gestire molti strumenti, specificare molti metadati e riflettere attentamente sulle proprie dipendenze e sul pubblico di destinazione. Ma la ricompensa è grande. 

Se scrivi codice utile e lo impacchetti correttamente, le persone saranno in grado di installarlo facilmente e trarne vantaggio.