Installare e usare Python su Windows (parte 6/10).

Attenzione!

Queste guide non sono più aggiornate, e in alcuni punti sono proprio superate.
Il mio libro Python in Windows tratta questi temi, e molto altro, in modo molto più completo e aggiornato. 

Struttura di un progetto Python.

Abbiamo concluso la nostra rassegna delle cose essenziali da sapere per installare e usare Python su Windows. Ci occupiamo adesso di alcuni aspetti più secondari, opzionali e anche soggetti ai gusti personali di ciascuno. Cominciamo con alcuni consigli su come strutturare un progetto Python.

Il meccanismo degli import in Python.

Come è possibile intuire dagli esempi che abbiamo fatto finora, l’istruzione import serve per “importare” e quindi rendere utilizzabile un modulo (ossia uno script, un file .py, diciamo) nel vostro codice Python (ossia nel vostro modulo, nel vostro file .py, diciamo). Il nome che importate può corrispondere a qualsiasi cosa: un semplice script costituito da un modulo soltanto, o un largo framework composto da package e subpackage e decine di moduli.
Quando gli chiedete di importare “qualcosa”, Python compie una ricerca per trovare “qualcosa” che corrisponde al nome che gli avete chiesto: se non trova nulla, alla fine restituisce un errore ModuleNotFoundError: No module named .... La strategia di ricerca dei nomi da importare è piuttosto complessa e non è questa la sede per spiegarla nel dettaglio: la documentazione ufficiale è molto chiara in merito.
Tra i posti dove Python cerca i moduli (o i package) da importare, c’è naturalmente la libreria standard (la directory .../Lib/ della sua installazione) e quindi anche i pacchetti esterni installati con Pip (che si trovano di solito in .../Lib/site-packages). Infine, Python cerca anche in ., ovvero nella stessa directory in cui si trova il modulo che richiede l’import.
In altre parole, se avete due moduli a.py e b.py nella stessa directory, nel modulo a.py potete tranquillamente scrivere import b: Python lo troverà cercando in . (se poi la directory è un package, allora Python cercherà anche nelle sotto-directory: ma questo è un argomento più avanzato).
Allo stesso modo, se vi portate con la shell nella directory in cui stanno a.py e b.py, e aprite l’interprete interattivo Python (con py -3 per esempio), allora potete importare sia import a sia import b.
Ci sono diversi modi in cui potete modificare e personalizzare il meccanismo degli import di Python. In genere si tratta di tecniche avanzate, ma una è piuttosto popolare: potete impostare la variabile di ambiente PYTHONPATH. Questa variabile può contenere un elenco di path (separate da punto-e-virgola come di consueto) dove Python si impegna a cercare i moduli da importare. Per esempio, supponiamo che abbiate creato un modulo d:/test/a.py: potete seguire questa sessione della shell:
> :: assicuriamoci di NON essere in d:/test...
> cd d:/
D:\ > py
Python 3.7.0 ([etc]) on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import a  # non funziona perche' non siamo in d:/test
(...)
ModuleNotFoundError: No module named 'a'
>>> exit()
D:\ > set PYTHONPATH=d:\test
D:\ > py
Python 3.7.0 ([etc]) on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import a  # adesso funziona perche' guarda anche in PYTHONPATH
>>>
Lo stesso effetto, da “dentro” il codice Python, si può ottenere manipolando la variabile sys.path (che è una semplice lista Python):
D:\ > :: cancelliamo PYTHONPATH...
D:\ > set PYTHONPATH=
D:\ > py
Python 3.7.0 ([etc]) on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import a
(...)
ModuleNotFoundError: No module named 'a'
>>> import sys
>>> sys.path.append('d:/test')
>>> import a  # adesso funziona
>>>
Anche se è possibile modificare in questo modo il meccanismo di import di Python, in genere non bisognerebbe farlo. Se il codice per funzionare dipende da impostazioni specifiche del vostro ambiente di lavoro (per esempio PYTHONPATH), allora non funzionerà per nessun altro.

Come gestire il proprio codice Python.

Capito almeno in parte come funziona import, possiamo adesso rispondere alla domanda che spesso si pone chi inizia a programmare in Python: dove dovrei mettere il codice che scrivo?
In primo luogo, sicuramente non bisogna mettere i propri script nella installazione di Python (in .../Lib/ o .../Lib/site-packages per esempio), e neppure in un venv. In particolare, come abbiamo visto un venv dovrebbe essere ri-creabile solo a partire da un requirements.txt: quindi dovrebbe contenere solo Python stesso, la sua libreria standard e tutti i pacchetti installati con Pip.
Potete mettere i vostri script… dove preferite. Una buona idea probabilmente è metterli in una directory apposita, per esempio %USERPROFILE%/python_scripts o magari anche solo d:/scripts, come preferite. Questo vale per gli script stand-alone (composti da un solo modulo).
Un progetto Python più grande, composto da due o più moduli, dovrebbe sempre stare in una sua directory riservata (eventualmente potete renderla un package Python con l’aggiunta di un __init__.py, e aggiungere altre sub-directory/sub-package… ma sono argomenti avanzati). La directory di un progetto dovrebbe contenere solo ed esclusivamente materiale relativo a quel progetto: codice Python, documentazione, varie informazioni e così via. Vedremo tra poco come conviene strutturare un progetto Python. Per comodità, potete raccogliere tutte le directory/progetti in un solo posto: potrebbe essere %USERPROFILE%/python_projects, o anche solo d:/repos, come preferite (“repos” per “repository”, anche perché spesso vorrete monitorare i vostri progetti con un version control system come Git: ne parliamo tra poco).
E come fate, se volete usare (e quindi importare) uno dei vostri script, magari uno dei vostri progetti, dentro un altro script o un altro progetto? Ecco, questo a rigore non si dovrebbe mai fare. Se due o più moduli appartengono allo stesso progetto, allora vivono nella stessa directory e sono quindi liberamente importabili tra loro. Ma non si dovrebbe mai importare un modulo “estraneo” al progetto, collocato in un posto “qualsiasi”. Più precisamente: un modulo dovrebbe importare solo (a) i moduli della libreria standard, (b) i moduli dei pacchetti esterni installati con Pip, e © gli altri moduli dello stesso progetto/directory.
Di conseguenza, se avete scritto un progetto “A” (o anche solo un modulo stand-alone) e volete usarlo (importarlo) in un altro progetto “B”, il modo corretto di procedere sarebbe di pacchettizzare il progetto “A” così da renderlo installabile con Pip. Non è necessario arrivare fino al punto di caricare fisicamente il pacchetto su PyPI: bisognerebbe però creare una wheel del progetto “A” e poi installarla con Pip nel venv del progetto “B”. La guida ufficiale spiega nel dettaglio come si fa. Beninteso, se poi distribuite ad altri il vostro progetto “B” senza mai aver pubblicato (su PyPI o altrove) anche il progetto “A”, allora chi usa il vostro progetto avrà qualche prevedibile problema. Ma questo è un altro discorso.
S’intende che, se avete seguito fino a questo punto, avete già capito la scorciatoia per evitare tutto questo: basta settare PYTHONPATH in modo opportuno per far sì che Python ricerchi i moduli da importare anche in directory arbitrarie. Per esempio, se includete d:/scripts nella PYTHONPATH, allora qualsiasi vostro progetto potrà importare tutti i moduli contenuti in d:/scripts, senza bisogno di pacchettizzarli e installarli con Pip. Capite però anche che questa scorciatoia può funzionare solo per voi. In generale, non è la cosa giusta da fare.

Struttura di un progetto Python.

Ci soffermiamo ora brevemente su come bisognerebbe organizzare un progetto Python ovvero, fisicamente, la directory che contiene tutti i file relativi al progetto. Ci sono molte guide, template, coding styles, suggerimenti di vario tipo in merito, e molti dettagli dipendono dalle abitudini di ciascuno, dai tool di sviluppo utilizzati, etc. Inoltre, i framework complessi come Django hanno le loro peculiariarità e le loro raccomandazioni specifiche.
Questo è un template molto generico che potete adottare:
project -
         |- project -
         |           |- __init.py__
         |           |- (moduli Python del progetto)
         |           |
         |           |- tests -
         |                     |- __init.py__
         |                     |- test_*.py (moduli di test)
         |
         |- docs -
         |        |- conf.py
         |        |- index.rst
         |        |- (file .rst della documentazione)
         |
         |- README.rst
         |- requirements.txt
         |- (LICENSE, AUTHORS... meta-informazioni)
         |- (.gitignore... file necessari ai tool)
         |- setup.py
In primo luogo, come già detto ogni progetto dovrebbe avere una sua directory, che avrà il nome del progetto: deve essere un nome corto ma significativo (quindi non project, naturalmente!), e per convenzione è tutto minuscolo, senza underscore.
Dentro la directory-base c’è un’altra directory con lo stesso nome, dentro cui mettete i moduli Python che effettivamente compongono il progetto. Questa struttura (project/project/<vari file py>) permette di tenere separati nella directory-base tutti i file di meta-informazione necessari (documentazione etc., come vedremo).
La directory “interna” (project/project) è destinata al codice: in genere si preferisce rendere questa directory un package Python, e pertanto si include anche un file __init__.py (che in genere è vuoto: se non sapete che cosa sono i package, dovreste leggervi la documentazione in merito). Se il vostro progetto è un package, è possibile strutturarlo ulteriormente in sub-packages (che fisicamente sono delle sub-directory di project/project, ciascuno con il suo __init__.py). Ma non è obbligatorio usare i package: potete benissimo limitarvi a riempire project/project con i moduli Python che compongono il vostro progetto, senza __init__.py. Se poi il vostro progetto si compone di un solo file, allora non ha molto senso neppure usare la directory interna project/project: potete mettere il vostro script direttamente nella directory-base project.
Nella directory interna project/project trova spazio una sub-directory project/project/tests che contiene i moduli dei test del vostro progetto. Gli “unit test” (e altri tipi di test) in Python sono praticamente un obbligo per ogni progetto serio: se non sapete nulla sull’argomento, potete iniziare dalla documentazione standard relativa. Per convenzione, i moduli dei test hanno un nome che inizia per test_. Se usate questa struttura, allora dovete usare i package, e quindi deve esserci sia project/project/__init__.py sia project/project/tests/__init__.py. Se non volete usare i package, allora non potete mettere i test in una sub-directory: potete metterli direttamente in project/project insieme al resto del codice. I moduli dei test si riconosceranno perché iniziano per test_.
Risaliamo alla directory principale project, e notiamo che contiene una sub-directory project/docs con la documentazione relativa al vostro progetto. Documentare bene il codice è un punto d’onore di ogni sviluppatore Python: nessuno vorrà avere a che fare con il vostro codice, se non è documentato. In teoria, siete liberi di decidere il formato, le strategie e gli strumenti per la vostra documentazione. In pratica però Sphinx è lo strumento più utilizzato nel mondo Python (anche per la documentazione ufficiale di Python stesso). Sphinx è un tool che genera diversi tipi di output (html, latex, etc.) a partire da testi scritti in reStructuredText. Nella vostra directory di documentazione project/docs non dovete includere l’output finale, ma solo i documenti reStructuredText (con estensione .rst) che servono a Sphinx per generare l’output, a partire da index.rst. In questo modo chiunque può generare l’output della vostra documentazione utilizzando il formato e il template di sua preferenza. Un file conf.py contiene i parametri necessari per Sphinx (leggete la documentazione per i dettagli).
Naturalmente non siete obbligati a usare Sphinx per la documentazione. Potete usare altri strumenti, mettere in project/docs dei semplici file di testo, o qualsiasi cosa volete: l’importante è che il progetto sia documentato.
La directory-base project contiene inoltre un numero variabile di file di meta-informazioni sul progetto. È praticamente obbligatorio un README (scritto così, senza estensioni; oppure README.txt o README.rst se è in reStructuredText, o README.md se è in Markdown, etc.). Il README contiene le informazioni essenziali, e dovrebbe essere scritto in Inglese (o come minimo avere una introduzione corta in Inglese all’inizio, se proprio non potete rinunciare all’Italiano). Un file LICENSE contiene il testo della licenza con cui volete distribuire il progetto. Non è necessario inserirlo però: se la licenza è standard e molto nota, basta un richiamo all’interno dei vostri moduli, e/o nel README. Altri file di meta-informazione comuni sono AUTHORS, CONTRIBUTORS, NEWS, history… tutti senza estensione o con estensioni variabili a seconda del formato.
Ci potrebbe poi essere un numero variabile di file (eventualmente auto-generati) necessari ai diversi tool di sviluppo che utilizzate. Per esempio, se usate Git per il controllo di versione, allora avrete una directory nascosta .git e probabilmente anche due file nascosti .gitconfig e .gitignore. Anche il vostro editor potrebbe appoggiarsi ad alcuni file nascosti nella directory-base per leggere delle impostazioni di configurazione.
Abbiamo già imparato a conoscere requirements.txt, che elenca i pacchetti che bisogna installare per far funzionare il vostro progetto in un virtual environment Python. Infine setup.py contiene le istruzioni necessarie a rendere il vostro progetto installabile con Pip (e quindi caricabile e distribuibile su PyPI): è un file essenziale se volete rendere pubblico il vostro lavoro. Per saperne di più su setup.py e tutte le operazioni necessarie, potete partire dalla documentazione standard.

Numeri di versione.

Assegnare un numero di versione a un progetto Python è importante, e occorre saperlo fare bene. Se distribuite il vostro progetto su PyPI, ogni volta che caricate degli aggiornamenti il numero di versione deve cambiare. Se il vostro progetto è su GitHub (o analogo), allora chiunque può clonarlo e usarlo anche in corrispondenza di commit intermedi tra due versioni: questo non è un problema, e non dovete certo modificare il numero di versione a ogni commit che fate. Tuttavia, deve essere ben chiaro a quali commit corrisponde un nuovo numero di versione (di solito, questo si ottiene taggando i commit) in modo che sia possibile clonare una versione ben precisa.
Il mondo Python ha adottato da tempo uno standard preciso per i numeri di versione, descritto nel dettaglio nella PEP 440. Detto in breve, si tratta del popolare schema “a tre numeri”: un numero di “major version”, uno di “minor version” e uno di “micro version” (o “bugfix release”). Per convenzione, dovreste sempre incrementare il primo numero (“major version”) quando introducete delle modifiche non retrocompatibili con il codice precedente. Potete incrementare solo il secondo numero quando introducete modifiche retrocompatibili e/o aggiungete funzionalità; è tollerabile qualche piccolo cambiamento non retrocompatibile anche tra una versione minore e l’altra. Infine, dovete incrementare solo il terzo numero quando non avete introdotto nessuna modifica che riguarda l’utente finale (nessun cambiamento di API, in altre parole), ma vi siete limitati a correggere errori e/o implementare altri miglioramenti nel codice (prestazioni, documentazione, etc.). La PEP 440 regola anche l’utilizzo di codici per indicare versioni alfa, beta, candidate, pre- e post- release, etc.
È utile pubblicare l’elenco di tutti i cambiamenti apportati tra una versione e la successiva. Se usate Git o un altro sistema per il controllo di versione, e se la vostra strategia di commit è corretta, spesso il log di Git è tutto ciò che vi serve per questo scopo.

Manuale di stile e controllo automatico dello stile.

Il manuale di stile ufficiale di Python è la PEP 8 che bisogna leggere e conoscere a fondo. I programmatori Python sono molto sensibili sull’argomento, e se volete far vedere il vostro codice è meglio seguire sempre la PEP 8. Molti grandi progetti hanno inoltre un loro manuale di stile interno: se decidete di collaborare con questi, è ovviamente necessario conformarsi.
Il processo di rilevazione automatica dei potenziali errori nel vostro codice e dei potenziali difetti di stile si chiama linting. Esistono diversi linter che fanno un ottimo lavoro nel segnalarvi dove il vostro codice non è conforme alla PEP 8. Il più semplice è Pep8, ma ci sono opzioni più evolute come PyLint. Sono pacchetti che si installano normalmente con Pip, e si possono usare dalla riga di comando della shell. Ve detto però che molti editor offrono qualche tipo di integrazione con uno o più linter: se l’editor trova il linter installato, può usarlo per segnalare automaticamente i problemi con colori o codici differenti. Parleremo più diffusamente degli editor e della loro configurazione tra poco.
Come abbiamo già detto, il vostro linter appartiene a quella categoria di pacchetti “ausiliari” che non è sbagliato installare globalmente, e rendere eventualmente accessibili ai venv dei progetti con l’opzione --system-site-packages al momento di creare il venv.

Commenti

Post più popolari