9. Importazioni
L'errore riscontrato nella versione 1 dell'esercizio dell'applicazione ci porta ad approfondire il ruolo dell'istruzione [import].

9.1. Script [import_01]
Lo script [imported] verrà importato da vari script (chiamati anche moduli):

Un modulo viene eseguito quando viene importato. Quindi, quando il modulo [importato] viene importato:
- verrà visualizzato il risultato della riga 3;
- alla variabile x nella riga 5 verrà assegnato il suo valore;
Lo script [main_01] è il seguente:
- Riga 2: Il modulo [imported] viene importato. Ciò ne causerà l'esecuzione:
- verrà visualizzato il valore 2;
- viene creata la variabile x con il valore 4;
- Riga 4: Viene utilizzata la variabile x del modulo importato;
In PyCharm viene segnalato un errore:
In [1], PyCharm indica che non riconosce il modulo [importato]. In termini tecnici, ciò significa che la cartella contenente il modulo [importato] non si trova nel Python Path di PyCharm. Il Python Path è l'insieme delle cartelle in cui vengono cercati i moduli importati. Per risolvere questo problema, è sufficiente impostare la cartella contenente il modulo [importato] come cartella [Root Sources], in questo caso la cartella [import/01]:


Dopo questo passaggio, la cartella [import/01] viene aggiunta al Python Path di PyCharm e l'errore scompare:

- in [1], la cartella [01] ha cambiato colore;
- in [2-3], non c'è più alcun errore;
I risultati dell'esecuzione sono i seguenti:
Commenti
- La riga 2 è il risultato dell'esecuzione del modulo importato;
- La riga 3 visualizza il valore della variabile x dal modulo importato;
Il concetto chiave da trarre da questo esempio è l'importanza del fatto che un modulo (o script) importato venga eseguito.
Lo script [main_02] è il seguente:
- La riga 2 utilizza una sintassi di importazione diversa [from module import object1, object2, …]. Qui importiamo la variabile [imported.x]. Con questa sintassi, la variabile x diventa una variabile dello script [main_02]. Non è più necessario anteporle il prefisso del modulo [imported];
- Riga 4: stampiamo la variabile x da [main_02];
I risultati dell'esecuzione sono i seguenti:
Lo script [main_03] è il seguente:
La notazione [import *] alla riga 2 significa che importiamo tutti gli oggetti visibili dal modulo importato (variabili, funzioni).
I risultati sono i seguenti:
Lo script [main_04] è il seguente:
La riga 3 mostra che possiamo importare un oggetto dal modulo imported e assegnargli un alias. Qui, la variabile [imported.x] diventa la variabile [main_04.y]. I risultati sono gli stessi di prima.
9.2. Script [import_02]

Il modulo importato [module1.py] è il seguente:
Il modulo importato definisce una funzione, il che è uno scenario comune.
Lo script [main_01] è il seguente:
- Riga 2: il modulo viene importato. Verrà eseguito. Qui non viene visualizzato nulla;
- riga 4: viene eseguita la funzione [f1] del modulo importato;
I risultati dell'esecuzione sono i seguenti:
Nota: per evitare che PyCharm segnali un errore durante l'importazione alla riga 2, è necessario inserire la cartella contenente [module1] nelle [Directory radice] di PyCharm:

In [1], la cartella [02] inserita nella directory [Root Sources] diventa blu. Si noti che l'errore segnalato non impedisce agli script di funzionare correttamente in questo caso. Infatti, quando viene eseguito lo script [main_0x], la cartella dello script viene automaticamente aggiunta al Python Path. Di conseguenza, [module1] viene trovato. D'ora in poi, quando una cartella appare in blu in uno screenshot, significa che è stata inserita nelle [Root Sources] di PyCharm.
Lo script [main_02] è il seguente:
- La riga 2 importa la funzione [f1] dal modulo [module1];
- Nella riga 4 viene utilizzata la funzione f1;
I risultati sono identici a quelli dello script [main_01].
9.3. Script [import_03]

Nota: [03] si trova nella cartella [Root Sources] del progetto.
I nuovi script importeranno il modulo [module2], che non si trova nella stessa cartella in cui si trovano loro.
Lo script [module2] è il seguente:
Lo script definisce quindi una funzione [f2].
Lo script [main_01] è il seguente:
- Riga 2: Utilizziamo una notazione speciale per indicare come individuare il modulo [module2]. [dir1.module2] va interpretato come il percorso [dir1/module2]: per trovare [module2], parti dalla cartella dello script corrente [main_01], poi entra in [dir1] e lì troverai [module2]. Tieni presente che il punto di partenza del percorso è la cartella dello script che sta importando;
- riga 4: per eseguire la funzione [f2] da [module2];
I risultati sono i seguenti:
Riga 2, il risultato della funzione [f2].
Lo script [main_02] è il seguente:
Nella riga 2, rinominiamo il modulo [dir1.module] per semplificare il codice nella riga 4.
Lo script [main_03] è il seguente:
Questa volta, alla riga 2, importiamo solo la funzione [f2], che diventa quindi una funzione dello script [main_03] (riga 4).
Tutti questi script funzionano altrettanto bene nell'ambiente PyCharm quanto in una console Python. Il motivo è che in entrambi i casi la directory dello script eseguito — in questo caso, la directory [03] — fa parte del Python Path. Di conseguenza, viene individuata la directory [dir1/module2].
9.4. Script [import_04]

Qui, le cartelle [dir1] e [dir2] sono state collocate nella [Root Sources] del progetto PyCharm.
Il primo modulo importato è [module3]:
Il secondo modulo importato è [module4]:
- Riga 1: Importiamo la funzione [f3] dal [module3]. In questo caso, [module3] è visibile perché abbiamo inserito la sua directory [dir1] nella cartella [Root Sources];
- Righe 4–6: Definiamo una funzione [f4] che chiama la funzione [f3] da [module3];
Lo script principale [main_01] è il seguente:
- Riga 2: importazione del modulo [module4]. Questo è visibile perché abbiamo inserito la sua directory [dir2] nelle [Root Sources] di PyCharm;
- Riga 4: esecuzione della funzione [f4] da [module4];
I risultati dell'esecuzione di [main_01] in PyCharm sono i seguenti:
Ora, eseguiamo [main_01] in un terminale Python (console):

I risultati sono i seguenti:
Cosa è successo? Il terminale Python non conosce il Python Path di PyCharm né le [Root Sources]. Ha un proprio Python Path. In questo percorso è sempre presente la cartella dello script in esecuzione, in questo caso lo script [main_01]. Conosce quindi la cartella [import/04]. Nello script in esecuzione trova la riga:
from module4 import f4
L'interprete Python cerca [module4] nelle cartelle del proprio Python Path. Tuttavia, [module4] non si trova in [import/04] — che è effettivamente nel Python Path — ma in [import/04/dir2], che non lo è. Da qui l'errore.
Quindi ci troviamo di fronte a un problema già incontrato in precedenza: uno script che funziona correttamente in PyCharm potrebbe bloccarsi in un terminale Python. Si tratta di un problema ricorrente che dovremo risolvere.
9.5. Script [import_05]

Nota: le cartelle [dir1] e [dir2] vengono aggiunte al Python Path. Si noti che qui c'è già un conflitto: [module3] e [module4] si troveranno in due posizioni nel Python Path di PyCharm:
- in [import/04/dir1] e [import/05/dir1] per [module3];
- nelle directory [import/04/dir2] e [import/05/dir2] per il [module4];
Possiamo quindi rimuovere [import/04/dir1] e [import/04/dir2] dalle [Root Sources] del progetto PyCharm. Si scopre che qui [import/05/dir1] è una copia di [import/04/dir1] (lo stesso vale per [dir2]), quindi non ci sono problemi. Tuttavia, vale la pena notare che all'interno di PyCharm stesso, è necessario prestare attenzione all'elenco delle cartelle in [Root Sources] per evitare conflitti.
Lo script [main_01] diventa il seguente:
Stiamo cercando di risolvere il problema del percorso Python. Vogliamo che funzioni altrettanto bene in PyCharm quanto in un terminale Python. Per farlo, lo configureremo noi stessi.
- Righe 4–6: aggiungiamo le directory [., ./dir1, ./dir2] al Python Path. Affinché ciò funzioni, la directory corrente in fase di esecuzione deve essere la directory [import/05]. Questo sarà vero in PyCharm ma non necessariamente in un terminale Python, come vedremo;
- Riga 8: importiamo [module4]. In base a quanto appena fatto, dovrebbe trovarsi in [./dir2];
L'esecuzione in PyCharm produce i seguenti risultati:
Ora, in un terminale Python:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\05>python main_01.py
f3
f4
Riga 1, la directory di esecuzione è [import/05].
Ora saliamo di un livello nell'albero delle directory da [import/05]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\05>cd ..
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import>python 05/main_01.py
Traceback (most recent call last):
File "05/main_01.py", line 8, in <module>
from module4 import f4
ModuleNotFoundError: No module named 'module4'
- Riga 2: Quando viene eseguito [main_01], non ci troviamo più nella cartella [import/05] ma in [import]. Tuttavia, abbiamo scritto:
sys.path.append(".")
sys.path.append("./dir1")
sys.path.append("./dir2")
Questo aggiunge le cartelle [import, import/dir1, import/dir2] al Python Path, il che non è affatto ciò che vogliamo. Si noti che l'aggiunta di cartelle inesistenti (import/dir1, import/dir2) al Python Path non causa errori.
Abbiamo fatto progressi, ma non è abbastanza. Dobbiamo aggiungere percorsi assoluti al Python Path, non relativi.
Lo script [main_02] è una variante di [main_01] che utilizza un file di configurazione [config.json]:
{
"dependencies": [
"C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/import/05/dir1",
"C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/import/05/dir2"
]
}
Il valore della chiave [dependencies] è l'elenco delle directory da aggiungere al Python Path. Si noti che in questo caso abbiamo utilizzato percorsi assoluti anziché percorsi relativi.
Lo script [main_02] utilizza il file [config.json] come segue:
- riga 6: si noti che abbiamo utilizzato il percorso assoluto del file di configurazione;
- righe 8–9: il file di configurazione viene letto. Dal suo contenuto viene costruito un dizionario [config] (riga 9);
- righe 11-13: gli elementi dell'array [config['dependencies']] vengono aggiunti al Python Path. Si noti che, poiché in [config.json] sono stati utilizzati nomi di cartelle assoluti, al Python Path vengono aggiunti nomi assoluti;
- riga 16: [module4] viene importato. Dovrebbe essere trovato poiché [dir2] è ora nel Python Path;
L'esecuzione produce gli stessi risultati di [main_02], tranne per il fatto che lo script continua a funzionare anche quando la directory di esecuzione non è più [import/05]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import>python 05/main_02.py
f3
f4
Riga 1, la directory di esecuzione è [import].
Abbiamo fatto progressi. Abbiamo visto:
- che dovevamo creare il Python Path da soli;
- che dovevamo includere i percorsi assoluti di tutte le cartelle contenenti i moduli importati dall'applicazione;
Tuttavia, inserire i percorsi assoluti negli script non è una soluzione. Non appena il progetto viene spostato su un altro sito, smette di funzionare. Dobbiamo trovare un altro modo.
9.6. Script [import_06]

Nota: le cartelle [06, dir1, dir2] sono state inserite nella directory [Root Sources] del progetto PyCharm. Le cartelle [dir1, dir2] sono identiche a quelle degli esempi precedenti.
Il file [config.json] è il seguente:
{
"rootDir": "C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06",
"relativeDependencies": [
"dir1",
"dir2"
],
"absoluteDependencies": [
]
}
Introduciamo due tipi di percorsi:
- percorsi assoluti, righe 7–8;
- percorsi relativi, righe 3–6. Questi sono relativi alla radice alla riga 2. Pertanto, quando il progetto viene spostato in una nuova posizione, è necessario modificare solo questa riga;
Lo script [utils.py] utilizza il file [config.json] e costruisce il percorso Python:
- riga 8: la funzione [config_app] riceve il nome del file di configurazione come parametro;
- righe 12–14: il file di configurazione viene utilizzato per creare il dizionario [config];
- riga 20: [sys.path] è l'elenco delle directory nel Python Path;
- righe 17–20: le dipendenze relative dal file di configurazione vengono aggiunte al Python Path. Vengono aggiunte all’inizio dell’elenco [sys.path], riga 20. Questo perché quando Python cerca un modulo, esplora le directory in [sys.path] in ordine. Tuttavia, in questo documento, i moduli con lo stesso nome si troveranno in directory diverse all'interno di [sys.path]. Posizionando le dipendenze dell'applicazione all'inizio dell'array [sys.path], ci assicuriamo che queste vengano cercate prima delle altre directory in [sys.path] che potrebbero contenere moduli con lo stesso nome;
- righe 21–24: le dipendenze assolute del file di configurazione vengono aggiunte al Python Path;
- riga 26: viene restituita la configurazione dell'applicazione;
- righe 29–30: la funzione [get_scriptdir] restituisce il percorso assoluto della directory contenente lo script attualmente in esecuzione (quella in cui si trova la chiamata alla funzione);
Lo script principale [main] è il seguente:
- riga 4: viene importata la funzione [config_app]. Si noti che, poiché [utils] e [main] si trovano nella stessa directory, questa [import] funziona sempre. Questo perché la directory dello script principale viene automaticamente aggiunta al Python Path;
- righe 7–12: la funzione [display_path] visualizza l'elenco delle cartelle nel Python Path;
- riga 19: l'applicazione viene configurata. Si noti che il percorso assoluto del file di configurazione viene passato alla funzione [config_app]. Dopo questa istruzione, il Python Path è stato ricostruito;
- riga 22: importiamo [module4]. Grazie alla ricostruzione del Python Path, questo modulo verrà trovato;
- riga 24: viene eseguita la funzione [f4];
Nell'ambiente PyCharm, i risultati dell'esecuzione sono i seguenti:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/import/06/main.py
avant....------------------------------
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\06
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\fonctions\shared
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v01\shared
…
C:\Program Files\Python38\python38.zip
C:\Program Files\Python38\DLLs
C:\Program Files\Python38\lib
C:\Program Files\Python38
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages
après....------------------------------
C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06/dir2
C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06/dir1
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\06
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\fonctions\shared
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v01\shared
….
C:\Program Files\Python38\python38.zip
C:\Program Files\Python38\DLLs
C:\Program Files\Python38\lib
C:\Program Files\Python38
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages
f3
f4
done
Process finished with exit code 0
Commenti
- righe 2–13: il percorso Python di PyCharm. Include tutte le cartelle presenti nella cartella [Root Sources] del progetto;
- righe 14–29: il percorso Python costruito dalla funzione [config_app]. Le righe 15–16 contengono le due dipendenze che abbiamo aggiunto;
- righe 22–27: le directory di sistema dell'interprete Python che ha eseguito lo script;
- righe 28–29: l'esecuzione procede normalmente;
Ora torniamo al contesto che in precedenza ha causato un errore di runtime:
- aprire un terminale Python;
- passare a una directory diversa da quella contenente lo script eseguito;
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import>python 06/main.py
avant....------------------------------
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\06
C:\Program Files\Python38\python38.zip
C:\Program Files\Python38\DLLs
C:\Program Files\Python38\lib
C:\Program Files\Python38
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages
après....------------------------------
C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06/dir2
C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06/dir1
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\06
C:\Program Files\Python38\python38.zip
C:\Program Files\Python38\DLLs
C:\Program Files\Python38\lib
C:\Program Files\Python38
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages
f3
f4
done
Questa volta funziona (righe 20–21). Si noti che [sys.path] non contiene le stesse directory rispetto a quando si esegue il codice in PyCharm.
9.7. Script [import_07]
Miglioriamo la soluzione precedente in due modi:
- sostituiamo il file di configurazione [config.json] con uno script [config.py]. Infatti, il file JSON pone un problema significativo: non è possibile inserirvi commenti. Il dizionario [config.json] può essere sostituito da un dizionario Python, che ha il vantaggio di poter essere commentato;
- utilizziamo un modulo visibile a tutti i progetti Python presenti sul computer;
9.7.1. Installazione di un modulo a livello di macchina

Sopra, creiamo una cartella [packages/myutils] nel progetto PyCharm (i nomi non contano).
Lo script [myutils.py] è il seguente:
- righe 6–18: la funzione [set_syspath] crea un Python Path con l'elenco delle directory passate come parametro;
- righe 12–15: verifichiamo che la directory da aggiungere al Python Path esista;
Lo script [__init.py__] (due trattini bassi prima e dopo il nome; è obbligatorio) è il seguente:
from .myutils import set_syspath
Importiamo la funzione [set_syspath] dallo script [myutils]. La notazione [.myutils] si riferisce al percorso [./myutils], il che significa che lo script [myutils] si trova nella stessa directory di [__init.py]. Avremmo potuto usare la notazione [myutils]. Tuttavia, creeremo un modulo [myutils] a livello di macchina. Di conseguenza, la notazione [from myutils import set_syspath] diventerebbe ambigua. Si riferisce all'importazione dello script [myutils] dalla directory corrente o allo script [myutils] a livello di macchina? La notazione [.myutils] risolve questa ambiguità.
Lo script [setup.py] (anche in questo caso, il nome è fisso) è il seguente:
from setuptools import setup
setup(name='myutils',
version='0.1',
description='Utilitaire fixant le Python Path',
url='#',
author='st',
author_email='st@gmail.com',
license='MIT',
packages=['myutils'],
zip_safe=False)
In questo script descriviamo il modulo che stiamo per creare. In questo caso, lo creeremo localmente. Tuttavia, lo stesso processo viene utilizzato per creare un modulo distribuito ufficialmente (vedi |pypi|). I punti importanti da tenere a mente sono i seguenti:
- riga 3: il nome del modulo che si sta creando;
- riga 4: la versione del modulo;
- riga 5: la sua descrizione;
- righe 7–8: l'autore del modulo;
Per installare questo modulo a livello di macchina, procedere come segue:

Quindi, nel terminale Python, digitare il seguente comando [pip install .]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install .
Processing c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\packages
Using legacy setup.py install for myutils, since package 'wheel' is not installed.
Installing collected packages: myutils
Attempting uninstall: myutils
Found existing installation: myutils 0.1
Uninstalling myutils-0.1:
Successfully uninstalled myutils-0.1
Running setup.py install for myutils ... done
Successfully installed myutils-0.1
D'ora in poi, qualsiasi script sul computer potrà importare il modulo [myutils] senza che questo sia presente nel codice del progetto.
9.7.2. Lo script [config.py]

Lo script [config.py] gestisce la configurazione dell'applicazione:
- Riga 1: la funzione [configure] gestisce la configurazione dell'applicazione;
- righe 7–10: il dizionario che prima si trovava in [config.json];
- righe 9-10: dato che siamo in uno script, possiamo accedere direttamente ai nomi assoluti delle cartelle [dir1, dir2];
- righe 12–14: utilizziamo la funzione [set_syspath] del modulo [myutils] appena creato per impostare il Python Path per la configurazione;
- riga 20: restituiamo il dizionario di configurazione dell'applicazione. Qui è vuoto;
9.7.3. Lo script [main.py]
Lo script principale [main] è il seguente:
- Righe 2–4: Configuriamo l'applicazione utilizzando il modulo [config.py]. Questo è accessibile perché si trova nella stessa directory dello script principale. Tuttavia, la directory dello script principale fa sempre parte del Python Path;
- quando arriviamo alla riga 6, il Python Path è stato costruito in modo da includere la cartella del modulo [module4]. Possiamo quindi importarlo alla riga 7;
- righe 10–15: non resta che eseguire la funzione [f4];
I risultati dell'esecuzione in PyCharm sono i seguenti:
In un terminale Python al di fuori della directory dello script principale, i risultati sono i seguenti:
D'ora in poi, seguiremo sempre la stessa procedura per configurare un'applicazione:
- la presenza di uno script [config.py] nella directory dello script principale. Questo script contiene una funzione [configure] che ha due scopi:
- costruire il Python Path per l'applicazione. Per farlo, [config.py] elenca tutte le cartelle contenenti i moduli utilizzati dall'applicazione e costruisce il Python Path utilizzando i loro percorsi assoluti;
- creare il dizionario [config] per la configurazione dell'applicazione;
Applichiamo questo approccio alla seconda versione dell'esercizio pratico. Come forse ricorderete, la versione 1 funzionava nell'ambiente PyCharm ma non in un terminale Python. Il problema era dovuto al percorso Python.