Come scrivere, creare pacchetti e distribuire una libreria in Python

Python è un ottimo linguaggio di programmazione, ma il packaging è uno dei suoi punti deboli. È un fatto ben noto nella comunità. L'installazione, l'importazione, l'utilizzo e la creazione di pacchetti è migliorata molto nel corso degli anni, ma non è ancora alla pari con i linguaggi più recenti come Go and Rust che hanno imparato molto dalle lotte di Python e di altri linguaggi maturi. 

In questo tutorial imparerai tutto ciò che devi sapere su come scrivere, impacchettare e distribuire i tuoi pacchetti. 

Come scrivere una libreria Python

Una libreria Python è una collezione coerente di moduli Python che è organizzata come un pacchetto Python. In generale, ciò significa che tutti i moduli vivono nella stessa directory e che questa directory si trova sul percorso di ricerca di Python. 

Scriviamo velocemente un piccolo pacchetto Python 3 e illustriamo tutti questi concetti.

Il pacchetto di patologia

Python 3 ha un eccellente oggetto Path, che è un enorme miglioramento rispetto al difficile modulo os.path di Python 2. Ma manca una capacità cruciale: trovare il percorso dello script corrente. Questo è molto importante quando si desidera individuare i file di accesso relativi allo script corrente. 

In molti casi, lo script può essere installato in qualsiasi posizione, quindi non è possibile utilizzare percorsi assoluti e la directory di lavoro può essere impostata su qualsiasi valore, quindi non è possibile utilizzare un percorso relativo. Se si desidera accedere a un file in una sottodirectory o directory principale, è necessario essere in grado di capire la directory di script corrente. 

Ecco come lo fai in Python:

import pathlib script_dir = pathlib.Path (__ file __). parent.resolve ()

Per accedere a un file chiamato "file.txt" in una sottodirectory "data" della directory dello script corrente, puoi usare il seguente codice: stampa (aperta (str (SCRIPT_DIR / 'i dati / file.txt'). read ())

Con il pacchetto di patologie, hai un built-in SCRIPT_DIR metodo, e tu lo usi in questo modo:

da pathology.Path import script_dir print (aperto (str (script_dir () / 'data / file.txt'). read ()) 

Sì, è un boccone. Il pacchetto patologico è molto semplice. Deriva la sua classe Path dal Pathlib Path e aggiunge uno statico SCRIPT_DIR () che restituisce sempre il percorso dello script chiamante. 

Ecco l'implementazione:

import pathlib import inspect class Path (type (pathlib.Path ())): @staticmethod def script_dir (): print (inspect.stack () [1] .filename) p = pathlib.Path (inspect.stack () [1 ] .filename) return p.parent.resolve () 

A causa dell'implementazione multipiattaforma di pathlib.Path, puoi derivarne direttamente da esso e deve derivare da una sottoclasse specifica (PosixPath o windowspath). La risoluzione dir dello script usa il modulo inspect per trovare il chiamante e quindi il suo attributo filename.

Test del pacchetto di patologia

Ogni volta che scrivi qualcosa che è più di un copione usa e getta, dovresti testarlo. Il modulo di patologia non fa eccezione. Ecco i test che utilizzano il framework di test unitario standard: 

import OS import shutil da unestest import TestCase da pathology.path import Path class PathTest (TestCase): def test_script_dir (self): expected = os.path.abspath (os.path.dirname (__ file__)) actual = str (Path.script_dir ()) self.assertEqual (previsto, effettivo) def test_file_access (self): script_dir = os.path.abspath (os.path.dirname (__ file__)) subdir = os.path.join (script_dir, 'test_data') se Path (subdir) .is_dir (): shutil.rmtree (subdir) os.makedirs (subdir) file_path = str (Path (sottodirectory) / 'file.txt') content = '123' aperto (file_path, 'w'). (contenuto) test_path = Path.script_dir () / subdir / 'file.txt' actual = open (str (test_path)). read () self.assertEqual (content, actual) 

Il percorso Python

I pacchetti Python devono essere installati da qualche parte sul percorso di ricerca di Python per essere importati dai moduli Python. Il percorso di ricerca di Python è un elenco di directory ed è sempre disponibile in sys.path. Ecco il mio attuale percorso sys:

>>> print ('\ n'.join (sys.path)) /Users/gigi.sayfan/miniconda3/envs/py3/lib/python36.zip /Users/gigi.sayfan/miniconda3/envs/py3/lib/ python3.6 /Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/lib-dynload /Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages / Users / gigi.sayfan / miniconda3 / ENV / PY3 / lib / python3.6 / site-packages / setuptools-27.2.0-py3.6.egg 

Si noti che la prima riga vuota dell'output rappresenta la directory corrente, quindi è possibile importare i moduli dalla directory di lavoro corrente, qualunque essa sia. È possibile aggiungere o rimuovere directory direttamente da / su sys.path. 

Puoi anche definire a PYTHONPATH variabile d'ambiente, e ci sono alcuni altri modi per controllarlo. Lo standard site-packages è incluso di default, ed è qui che i pacchetti che installi usando via pip go. 

Come confezionare una libreria Python

Ora che abbiamo il nostro codice e test, impacchettiamo tutto in una libreria adeguata. Python fornisce un modo semplice tramite il modulo di installazione. Crei un file chiamato setup.py nella directory principale del tuo pacchetto. Quindi, per creare una distribuzione di origine, esegui: python setup.py sdist

Per creare una distribuzione binaria chiamata ruota, esegui: python setup.py bdist_wheel

Ecco il file setup.py del pacchetto patologia:

da setuptools import setup, find_packages setup (name = 'pathology', version = "0.1", url = "https://github.com/the-gigi/pathology", license = "MIT", author = "Gigi Sayfan" , author_email = "[email protected]", description = "Aggiungi il metodo statico script_dir () al percorso", packages = find_packages (exclude = ['tests']), long_description = open ('README.md'). read (), zip_safe = False)

Include molti metadati oltre all'elemento 'packages' che usa il find_packages () funzione importata da setuptools per trovare sotto-pacchetti.

Costruiamo una distribuzione di origine:

$ python setup.py sdist esegue sdist esegue egg_info crea pathology.egg -info scrive pathology.egg-info / PKG-INFO scrive dependency_links in pathology.egg-info / dependency_links.txt scrive i nomi di livello superiore in pathology.egg-info / top_level.txt che scrive il file manifest 'pathology.egg-info / SOURCES.txt' legge file manifest 'pathology.egg-info / SOURCES.txt' scrivi manifest file 'pathology.egg-info / SOURCES.txt' warning: sdist: standard file non trovato: dovrebbe avere uno dei file README, README.rst, README.txt che eseguono il controllo creando la patologia-0.1 creando pathology-0.1 / pathology creando pathology-0.1 / pathology.egg-info copiando i file in pathology-0.1 ... copying setup.py -> patologia-0.1 patologia di copia / __ init__.py -> patologia-0.1 / patologia copia patologia / path.py -> patologia-0.1 / patologia copia patologia.egg-info / PKG-INFO -> patologia-0.1 / patologia.egg -info copying pathology.egg-info / SOURCES.txt -> pathology-0.1 / pathology.egg-info copying pathology.egg-info / dependency_links.txt -> patologia -0.1 / pathology.egg-info copying pathology.egg-info / not-zip-safe -> pathology-0.1 / pathology.egg-info copying pathology.egg -info / top_level.txt -> pathology-0.1 / pathology.egg -info Scrittura pathology-0.1 / setup.cfg creando dist Creazione di archivio tar rimuovendo 'pathology-0.1' (e tutto sotto di esso)

L'avviso è perché ho usato un file README.md non standard. È sicuro da ignorare. Il risultato è un file tar-gzip sotto la directory dist:

$ ls -la dist totale 8 drwxr-xr-x 3 gigi.sayfan gigi.sayfan 102 Apr 18 21:20. drwxr-xr-x 12 gigi.sayfan gigi.sayfan 408 apr 18 21: 20 ... -rw-r - r-- 1 gigi.sayfan gigi.sayfan 1223 apr 18 21:20 pathology-0.1.tar.gz

Ed ecco una distribuzione binaria:

$ python setup.py bdist_wheel esecuzione bdist_wheel esecuzione build esecuzione build_py creazione creazione creazione build / lib creazione build / lib / patologia copia patologia / __ init__.py -> build / lib / pathology copia pathology / path.py -> build / lib / pathology installazione per compilare / bdist.macosx-10.7-x86_64 / ruota con installazione in esecuzione con install_lib creando build / bdist.macosx-10.7-x86_64 creando build / bdist.macosx-10.7-x86_64 / ruota creando build / bdist.macosx-10.7-x86_64 / ruota / patologia copiatura build / lib / patologia / __ init__.py -> build / bdist.macosx-10.7-x86_64 / wheel / patologia copia build / lib / pathology / path.py -> build / bdist.macosx-10.7-x86_64 / ruota / patologia eseguendo install_egg_info eseguendo egg_info scrivendo pathology.egg-info / PKG-INFO scrivendo dependency_links in pathology.egg-info / dependency_links.txt scrivendo i nomi di livello superiore in pathology.egg-info / top_level.txt leggendo il file manifest 'pathology. egg-info / SOURCES.txt 'scrittura file manifest' pathology.egg-info / SOURCES.txt 'Copia pathology.egg-info per bui ld / bdist.macosx-10.7-x86_64 / wheel / pathology-0.1-py3.6.egg-info che esegue install_scripts creando build / bdist.macosx-10.7-x86_64 / wheel / pathology-0.1.dist-info / WHEEL

Il pacchetto di patologia contiene solo moduli Python puri, quindi è possibile creare un pacchetto universale. Se il tuo pacchetto include estensioni C, dovrai costruire una ruota separata per ogni piattaforma:

$ ls -la dist totale 16 drwxr-xr-x 4 gigi.sayfan gigi.sayfan 136 Apr 18 21:24. drwxr-xr-x 13 gigi.sayfan gigi.sayfan 442 apr 18 21: 24 ... -rw-r - r-- 1 gigi.sayfan gigi.sayfan 2695 apr 18 21:24 pathology-0.1-py3-none-any .whl -rw-r - r-- 1 gigi.sayfan gigi.sayfan 1223 apr 18 21:20 pathology-0.1.tar.gz 

Per approfondire l'argomento del packaging delle librerie Python, consulta Come scrivere i tuoi pacchetti Python.

Come distribuire un pacchetto Python

Python ha un repository di pacchetti centrale chiamato PyPI (Python Packages Index). Quando si installa un pacchetto Python usando pip, verrà scaricato il pacchetto da PyPI (a meno che non si specifichi un repository diverso). Per distribuire il nostro pacchetto di patologie, è necessario caricarlo su PyPI e fornire alcuni extra metadata richiesti da PyPI. I passaggi sono:

  • Crea un account su PyPI (solo una volta).
  • Registra il tuo pacco.
  • Carica il tuo pacco.

Crea un account

È possibile creare un account sul sito Web PyPI. Quindi creare un .pypirc file nella tua home directory:

[distutils] index-servers = pypi [pypi] repository = https://pypi.python.org/pypi username = the_gigi 

A scopo di test, puoi aggiungere un server di indicizzazione "pypitest" al tuo .pypirc file:

[distutils] index-servers = pypi pypitest [pypitest] repository = https://testpypi.python.org/pypi username = the_gigi [pypi] repository = https://pypi.python.org/pypi username = the_gigi

Registra il tuo pacchetto

Se questa è la prima versione del pacchetto, è necessario registrarlo con PyPI. Utilizzare il comando register di setup.py. Ti chiederà la tua password. Si noti che l'ho indirizzato al repository di test qui:

$ python setup.py register -r pypitest registro in esecuzione eseguendo egg_info scrivendo pathology.egg-info / PKG-INFO scrivendo dependency_links in pathology.egg-info / dependency_links.txt scrivendo nomi di primo livello in pathology.egg-info / top_level.txt leggendo il file manifest 'pathology.egg-info / SOURCES.txt' scrivendo file manifest 'pathology.egg-info / SOURCES.txt' controllo di esecuzione Password: registrazione di patologia a https://testpypi.python.org/pypi Risposta del server (200 ): OK

Carica il tuo pacchetto

Ora che il pacchetto è registrato, possiamo caricarlo. Raccomando l'uso dello spago, che è più sicuro. Installalo come al solito usando pip installare cordicella. Quindi carica il tuo pacco usando spago e fornisci la tua password (redatta sotto):

$ spago upload -r pypitest -p  dist / * Caricamento delle distribuzioni su https://testpypi.python.org/pypi Caricamento pathology-0.1-py3-none-any.whl [==================== ============] 5679/5679 - 00:00:02 Upload pathology-0.1.tar.gz [=================== =============] 4185/4185 - 00:00:01 

Per approfondire l'argomento della distribuzione dei pacchetti, consulta Come condividere i pacchetti Python.

Conclusione

In questo tutorial, abbiamo affrontato il processo completo di scrivere una libreria Python, confezionarla e distribuirla tramite PyPI. A questo punto, dovresti avere tutti gli strumenti per scrivere e condividere le tue librerie con il mondo.

Inoltre, non esitare a vedere ciò che abbiamo a disposizione per la vendita e per studiare nel mercato, e per favore fai tutte le domande e fornisci il tuo prezioso feedback usando il feed qui sotto.