() translation by (you can also view the original English article)
Python è un bel linguaggio di programmazione ma, la pacchettizzazione è uno dei suoi punti deboli. E' un fatto ben conosciuto nella community. L'installazione, l'importazione, l'uso e la creazione di pacchetti è migliorato molto nel corso degli anni ma, non è ancora alla pari con i nuovi linguaggi come Go e Rust che hanno imparato molto dalle lotte di Python e altri linguaggi maturi.
In questo tutorial, imparerai tutto ciò di cui hai bisogno di sapere per scrivere, pacchettizzare e distribuire i tuoi propri pacchetti.
Come scrivere una libreria Python
Una libreria Python è un insieme coerente di moduli Python che sono organizzati come un pacchetto Python. In generale, ciò significa che tutti i moduli vivono sotto la stessa cartella e che questa cartella è sul percorso di ricerca di Python.
Scriviamo rapidamente un piccolo pacchetto Python 3 e illustriamo tutti questi concetti.
Il pacchetto Pathology
Python 3 dispone di un eccellente oggetto Path, che è un miglioramento enorme sopra il modulo imbarazzante di os.path di Python 2. Ma non è presente una funzionalità fondamentale — 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 cartella di lavoro può essere impostata su qualsiasi valore, quindi non è possibile utilizzare un percorso relativo. Se si desidera accedere a un file in una directory sotto-cartella o in quella genitore, devi essere in grado di capire la cartella attuale dello script.
Ecco come farlo in Python:
1 |
import pathlib |
2 |
|
3 |
script_dir = pathlib.Path(__file__).parent.resolve() |
Per accedere ad un file denominato 'file.txt' in una sotto-cartella 'dati' della cartella dello script corrente, puoi utilizzare il seguente codice: print(open(str(script_dir/'data/file.txt').read())
Con il pacchetto pathology, hai un metodo incorporato script_dir e puoi utilizzarlo così:
1 |
from pathology.Path import script_dir |
2 |
|
3 |
print(open(str(script_dir()/'data/file.txt').read()) |
4 |
Sì, è un boccone. Il pacchetto pathology è molto semplice. Deriva la propria classe Path dal percorso di pathlib e aggiunge un script_dir() statico che restituisce sempre il percorso dello script chiamante.
Ecco l'implementazione:
1 |
import pathlib |
2 |
import inspect |
3 |
|
4 |
class Path(type(pathlib.Path())): |
5 |
@staticmethod |
6 |
def script_dir(): |
7 |
print(inspect.stack()[1].filename) |
8 |
p = pathlib.Path(inspect.stack()[1].filename) |
9 |
return p.parent.resolve() |
A causa dell'implementazione di varie piattaforme di pathlib.Path, puoi derivare direttamente da esso e devi derivare da una specifica sottoclasse (PosixPath o WindowsPath). La risoluzione di script dir utilizza il modulo di ispezione per trovare il chiamante e quindi il relativo attributo filename.
Test del pacchetto Pathology
Ogni volta che scrivi qualcosa che è più di uno script usa e getta, dovresti testarlo. Il modulo pathology non c'è eccezione. Qui ci sono le prove utilizzando il framework standard unit test:
1 |
import os |
2 |
import shutil |
3 |
from unittest import TestCase |
4 |
from pathology.path import Path |
5 |
|
6 |
|
7 |
class PathTest(TestCase): |
8 |
def test_script_dir(self): |
9 |
expected = os.path.abspath(os.path.dirname(__file__)) |
10 |
actual = str(Path.script_dir()) |
11 |
self.assertEqual(expected, actual) |
12 |
|
13 |
def test_file_access(self): |
14 |
script_dir = os.path.abspath(os.path.dirname(__file__)) |
15 |
subdir = os.path.join(script_dir, 'test_data') |
16 |
if Path(subdir).is_dir(): |
17 |
shutil.rmtree(subdir) |
18 |
os.makedirs(subdir) |
19 |
file_path = str(Path(subdir)/'file.txt') |
20 |
content = '123' |
21 |
open(file_path, 'w').write(content) |
22 |
test_path = Path.script_dir()/subdir/'file.txt' |
23 |
actual = open(str(test_path)).read() |
24 |
|
25 |
self.assertEqual(content, actual) |
Il percorso di Python
I pacchetti Python devono essere installati da qualche parte nel percorso di ricerca di Python per essere importati come moduli Python. Il percorso di ricerca di Python è un elenco di cartelle ed è sempre disponibile in sys.path
. Ecco il mio sys.path corrente:
1 |
>>> print('\n'.join(sys.path)) |
2 |
|
3 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python36.zip |
4 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6 |
5 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/lib-dynload |
6 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages |
7 |
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages/setuptools-27.2.0-py3.6.egg |
Nota che la prima riga vuota dell'output rappresenta la cartella corrente, quindi è possibile importare i moduli dalla cartella corrente di lavoro, qualunque essa sia. Puoi direttamente aggiungere o rimuovere le cartelle da sys.path.
È inoltre possibile definire una variabile d'ambiente PYTHONPATH e ci sono alcuni altri modi per controllarlo. Lo standard site-packages
è incluso già, e questo è dove vanno i pacchetti che si installano tramite pip.
Come pacchettizzare una libreria Python
Ora che abbiamo il nostro codice e test, pacchettizziamo tutto in una vera e propria libreria. Python fornisce un modo semplice tramite il modulo di installazione. Puoi creare un file chiamato setup.py nella cartella principale del tuo pacchetto. Poi, per creare una distribuzione del sorgente, puoi eseguire: python setup.py sdist
Per creare una distribuzione binaria chiamata wheel, puoi eseguire: python setup.py bdist_wheel
Ecco il file setup.py del pacchetto di pathology:
1 |
from setuptools import setup, find_packages |
2 |
|
3 |
setup(name='pathology', |
4 |
version='0.1', |
5 |
url='https://github.com/the-gigi/pathology', |
6 |
license='MIT', |
7 |
author='Gigi Sayfan', |
8 |
author_email='the.gigi@gmail.com', |
9 |
description='Add static script_dir() method to Path', |
10 |
packages=find_packages(exclude=['tests']), |
11 |
long_description=open('README.md').read(), |
12 |
zip_safe=False) |
Esso comprende un sacco di metadati oltre la voce 'packages' che utilizza la funzione find_packages()
importata da setuptools
per trovare sotto-pacchetti.
Costruiamo una distribuzione del codice sorgente:
1 |
$ python setup.py sdist |
2 |
running sdist |
3 |
running egg_info |
4 |
creating pathology.egg-info |
5 |
writing pathology.egg-info/PKG-INFO |
6 |
writing dependency_links to pathology.egg-info/dependency_links.txt |
7 |
writing top-level names to pathology.egg-info/top_level.txt |
8 |
writing manifest file 'pathology.egg-info/SOURCES.txt' |
9 |
reading manifest file 'pathology.egg-info/SOURCES.txt' |
10 |
writing manifest file 'pathology.egg-info/SOURCES.txt' |
11 |
warning: sdist: standard file not found: should have one of README, README.rst, README.txt |
12 |
|
13 |
running check |
14 |
creating pathology-0.1 |
15 |
creating pathology-0.1/pathology |
16 |
creating pathology-0.1/pathology.egg-info |
17 |
copying files to pathology-0.1... |
18 |
copying setup.py -> pathology-0.1 |
19 |
copying pathology/__init__.py -> pathology-0.1/pathology |
20 |
copying pathology/path.py -> pathology-0.1/pathology |
21 |
copying pathology.egg-info/PKG-INFO -> pathology-0.1/pathology.egg-info |
22 |
copying pathology.egg-info/SOURCES.txt -> pathology-0.1/pathology.egg-info |
23 |
copying pathology.egg-info/dependency_links.txt -> pathology-0.1/pathology.egg-info |
24 |
copying pathology.egg-info/not-zip-safe -> pathology-0.1/pathology.egg-info |
25 |
copying pathology.egg-info/top_level.txt -> pathology-0.1/pathology.egg-info |
26 |
Writing pathology-0.1/setup.cfg |
27 |
creating dist |
28 |
Creating tar archive |
29 |
removing 'pathology-0.1' (and everything under it) |
L'avviso è perché ho usato un file README.md non standard. È possibile ignorare. Il risultato è un file tar-gzip sotto la cartella dist:
1 |
$ ls -la dist |
2 |
total 8 |
3 |
drwxr-xr-x 3 gigi.sayfan gigi.sayfan 102 Apr 18 21:20 .
|
4 |
drwxr-xr-x 12 gigi.sayfan gigi.sayfan 408 Apr 18 21:20 .. |
5 |
-rw-r--r-- 1 gigi.sayfan gigi.sayfan 1223 Apr 18 21:20 pathology-0.1.tar.gz
|
Ed ecco una distribuzione binaria:
1 |
$ python setup.py bdist_wheel |
2 |
running bdist_wheel |
3 |
running build |
4 |
running build_py |
5 |
creating build |
6 |
creating build/lib |
7 |
creating build/lib/pathology |
8 |
copying pathology/__init__.py -> build/lib/pathology |
9 |
copying pathology/path.py -> build/lib/pathology |
10 |
installing to build/bdist.macosx-10.7-x86_64/wheel |
11 |
running install |
12 |
running install_lib |
13 |
creating build/bdist.macosx-10.7-x86_64 |
14 |
creating build/bdist.macosx-10.7-x86_64/wheel |
15 |
creating build/bdist.macosx-10.7-x86_64/wheel/pathology |
16 |
copying build/lib/pathology/__init__.py -> build/bdist.macosx-10.7-x86_64/wheel/pathology |
17 |
copying build/lib/pathology/path.py -> build/bdist.macosx-10.7-x86_64/wheel/pathology |
18 |
running install_egg_info |
19 |
running egg_info |
20 |
writing pathology.egg-info/PKG-INFO |
21 |
writing dependency_links to pathology.egg-info/dependency_links.txt |
22 |
writing top-level names to pathology.egg-info/top_level.txt |
23 |
reading manifest file 'pathology.egg-info/SOURCES.txt' |
24 |
writing manifest file 'pathology.egg-info/SOURCES.txt' |
25 |
Copying pathology.egg-info to build/bdist.macosx-10.7-x86_64/wheel/pathology-0.1-py3.6.egg-info |
26 |
running install_scripts |
27 |
creating build/bdist.macosx-10.7-x86_64/wheel/pathology-0.1.dist-info/WHEEL |
Il pacchetto pathology contiene solo moduli puri di Python, così un pacchetto universale può essere costruito. Se il pacchetto include estensioni di C, dovrete costruire un wheel separato per ogni piattaforma:
1 |
$ ls -la dist |
2 |
total 16 |
3 |
drwxr-xr-x 4 gigi.sayfan gigi.sayfan 136 Apr 18 21:24 .
|
4 |
drwxr-xr-x 13 gigi.sayfan gigi.sayfan 442 Apr 18 21:24 .. |
5 |
-rw-r--r-- 1 gigi.sayfan gigi.sayfan 2695 Apr 18 21:24 pathology-0.1-py3-none-any.whl
|
6 |
-rw-r--r-- 1 gigi.sayfan gigi.sayfan 1223 Apr 18 21:20 pathology-0.1.tar.gz
|
Per una più profonda immersione nel tema di pacchettizzazione di una libreria Python, scopri come scrivere i tuoi propri pacchetti Python.
Come distribuire un pacchetto Python
Python dispone di un repository di pacchetti centrale chiamato PyPI (Python Packages Index). Quando installi un pacchetto Python utilizzando pip, scaricherà il pacchetto da PyPI (a meno che non si specifichi un repository diverso). Per distribuire il nostro pacchetto di pathology, abbiamo bisogno di caricarlo su PyPI e fornire alcuni metadati aggiuntivi che richiede PyPI. I passaggi sono:
- Creare un account su PyPI (solo una volta).
- Registrare il tuo pacchetto.
- Caricare il pacchetto.
Creare un Account
È possibile creare un account sul sito web PyPI. Poi creare un file .pypirc nella vostra cartella principale:
1 |
[distutils] |
2 |
index-servers=pypi |
3 |
|
4 |
[pypi] |
5 |
repository = https://pypi.python.org/pypi |
6 |
username = the_gigi |
Per scopi di test, è possibile aggiungere un server di indice "pypitest" al file .pypirc:
1 |
[distutils] |
2 |
index-servers= |
3 |
pypi
|
4 |
pypitest
|
5 |
|
6 |
[pypitest] |
7 |
repository = https://testpypi.python.org/pypi |
8 |
username = the_gigi |
9 |
|
10 |
[pypi] |
11 |
repository = https://pypi.python.org/pypi |
12 |
username = the_gigi |
Registrare il tuo pacchetto
Se questa è la prima release del pacchetto, è necessario registrarlo con PyPI. Utilizza il comando register di setup.py. Ti chiederà la password. Nota che io punto al repository di prova qui:
1 |
$ python setup.py register -r pypitest |
2 |
running register |
3 |
running egg_info |
4 |
writing pathology.egg-info/PKG-INFO |
5 |
writing dependency_links to pathology.egg-info/dependency_links.txt |
6 |
writing top-level names to pathology.egg-info/top_level.txt |
7 |
reading manifest file 'pathology.egg-info/SOURCES.txt'
|
8 |
writing manifest file 'pathology.egg-info/SOURCES.txt'
|
9 |
running check |
10 |
Password: |
11 |
Registering pathology to https://testpypi.python.org/pypi |
12 |
Server response (200): OK |
Caricare il proprio pacchetto
Ora che il pacchetto è registrato, possiamo caricarlo. raccomando di utilizzare twine che è più sicuro. Installalo come al solito usando pip install twine
. Poi carica il pacchetto utilizzando twine e fornisci la tua password (redatta sotto):
1 |
$ twine upload -r pypitest -p <redacted> dist/* |
2 |
Uploading distributions to https://testpypi.python.org/pypi |
3 |
Uploading pathology-0.1-py3-none-any.whl |
4 |
[================================] 5679/5679 - 00:00:02 |
5 |
Uploading pathology-0.1.tar.gz |
6 |
[================================] 4185/4185 - 00:00:01 |
Per una più profonda immersione nel tema di distribuzione di pacchetti, scopri come condividere i tuoi pacchetti Python.
Conclusione
In questo tutorial, siamo andati attraverso il processo di vera e propria scrittura di una libreria Python, pacchettizzazione e distribuzione attraverso PyPI. A questo punto, dovresti avere tutti gli strumenti per scrivere e condividere le tue librerie con il mondo.
Inoltre, non esitate a vedere quello che abbiamo disponibile in vendita e per lo studio nel marketplace e, ti prego di fare tutte le domande e fornire il vostro prezioso feedback utilizzando il feed qui sotto.