Skip to content

9. Importy

Błąd napotkany w wersji 1 ćwiczenia praktycznego skłania nas do dokładniejszego przyjrzenia się roli instrukcji [import].

Image

9.1. Skrypty [import_01]

Skrypt [imported] będzie importowany przez różne skrypty (zwane również modułami):

Image


# moduł zaimportowany
# ta instrukcja zostanie wykonana za każdym razem, gdy moduł zostanie zaimportowany
print("2")
# zmienna należąca do zaimportowanego modułu
x=4

Moduł jest uruchamiany w momencie jego zaimportowania. Tak więc po zaimportowaniu modułu [imported]:

  • zostanie wyświetlony wiersz 3;
  • zmienna x w wierszu 5 otrzyma swoją wartość;

Skrypt [main_01] wygląda następująco:


# importowany moduł jest wykonywany
import imported
# użycie zmiennej x z importowanego modułu
print(imported.x)
  • wiersz 2: importowany jest moduł [imported]. Spowoduje to jego wykonanie:
    • zostanie wyświetlona wartość 2;
    • tworzona jest zmienna x o wartości 4;
  • wiersz 4: wykorzystuje się zmienną x z zaimportowanego modułu;

W module PyCharm zgłaszany jest błąd:

W module [1] moduł PyCharm zgłasza, że nie rozpoznaje modułu [imported]. Z technicznego punktu widzenia oznacza to, że folder zawierający moduł [imported] nie znajduje się w ścieżce Python Path modułu PyCharm. Ścieżka Python Path to zbiór folderów, w których szukane są importowane moduły. Aby rozwiązać ten problem, wystarczy zadeklarować w module [Sources root] folder, w którym znajduje się moduł [imported], w tym przypadku folder [import/01]:

Image

Image

Po wykonaniu tej operacji folder [import/01] zostaje dodany do ścieżki Python Path w Pycharmie, a błąd znika:

Image

  • w pliku [1] folder [01] zmienił kolor;
  • w [2-3] nie ma już błędu;

Wyniki wykonania są następujące:

1
2
3
4
5
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/01/main_01.py
2
4

Process finished with exit code 0

Komentarze

  • wiersz 2 zawiera wynik wykonania zaimportowanego modułu;
  • w wierszu 3 wyświetlana jest wartość zmiennej x z zaimportowanego modułu;

Z tego przykładu należy zapamiętać ważną zasadę, że zaimportowany moduł (lub skrypt) jest wykonywany.

Skrypt [main_02] wygląda następująco:


# importujemy zmienną x z importowanego modułu
from imported import x
# wyświetla się
print(x)
  • W wierszu 2 mamy inną składnię importu: [from module import objet1, objet2, …]. Tutaj importujemy zmienną [imported.x]. Dzięki tej składni zmienna x staje się zmienną skryptu [main_02]. Nie ma już potrzeby poprzedzania jej prefiksem modułu [imported];
  • wiersz 4: wyświetlana jest zmienna x ze skryptu [main_02];

Wyniki wykonania są następujące:

1
2
3
4
5
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/01/main_02.py
2
4

Process finished with exit code 0

Skrypt [main_03] wygląda następująco:


# importuje się wszystko, co jest widoczne z importowanego modułu
from imported import *
# używa się zmiennej x z importowanego modułu
print(x)

Zapis [import *] w wierszu 2 oznacza, że importowane są wszystkie widoczne obiekty z importowanego modułu (zmienne, funkcje).

Wyniki są następujące:

1
2
3
4
5
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/01/main_03.py
2
4

Process finished with exit code 0

Skrypt [main_04] wygląda następująco:


# importujemy zmienną x z importowanego modułu
# i zmieniamy jej nazwę na y
from imported import x as y
# wyświetla się zmienną y
print(y) 

Wiersz 3 pokazuje, że można zaimportować obiekt z importowanego modułu i nadać mu alias. W tym przypadku zmienna [imported.x] staje się zmienną [main_04.y]. Wyniki są takie same jak poprzednio.

9.2. Skrypt [import_02]

Image

Zaimportowany moduł [module1.py] wygląda następująco:


# funkcja
def f1():
    print("f1")

Zaimportowany moduł definiuje funkcję, co jest częstym przypadkiem.

Skrypt [main_01] ma następującą postać:


# import
import module1
# wykonanie funkcji f1
module1.f1()
  • w wierszu 2 moduł jest importowany. Zostanie on wykonany. W tym przypadku nie wyświetla on niczego;
  • wiersz 4: wykonywana jest funkcja [f1] z zaimportowanego modułu;

Wyniki wykonania są następujące:

1
2
3
4
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/02/main_01.py
f1

Process finished with exit code 0

Uwaga: Aby funkcja PyCharm nie zgłaszała błędu podczas importowania wiersza 2, należy umieścić folder zawierający plik [module1] w folderze [Sources Root], który znajduje się w folderze PyCharm:

Image

W pliku [1] folder [02] umieszczony w folderze [Sources Root] zmienił kolor na niebieski. Należy zauważyć, że zgłoszony błąd nie uniemożliwia w tym przypadku poprawnego wykonania skryptów. Podczas wykonywania skryptu [main_0x] katalog skryptu jest bowiem automatycznie dodawany do ścieżki Python Path. Dzięki temu skrypt [module1] zostaje znaleziony. Od tej pory, gdy na zrzucie ekranu folder jest zaznaczony na niebiesko, oznacza to, że został on umieszczony w ścieżce [Sources Root] przez skrypt PyCharm.

Skrypt [main_02] wygląda następująco:


# import
from module1 import f1
# wykonanie funkcji f1
f1()
  • w wierszu 2 importowana jest funkcja [f1] z modułu [module1];
  • w wierszu 4 używana jest funkcja f1;

Wyniki są identyczne jak w przypadku skryptu [main_01].

9.3. Skrypty [import_03]

Image

Uwaga: [03] znajduje się w folderze [Sources Root] projektu.

Nowe skrypty zaimportują moduł [module2], który nie znajduje się w tym samym folderze co one.

Skrypt [module2] ma następującą treść:


# funkcja
def f2():
    print("f2")

Skrypt definiuje zatem funkcję [f2].

Skrypt [main_01] ma następującą postać:


# import modułu Class2
import dir1.module2
# wykonanie f2
dir1.module2.f2()
  • wiersz 2: stosuje się specjalny zapis, aby wskazać, jak znaleźć moduł [module2]. Należy odczytać [dir1.module2] jako ścieżkę [dir1/module2]: aby znaleźć [module2], należy zacząć od folderu bieżącego skryptu [main_01], następnie przejść do [dir1], a tam znaleźć [module2]. Należy pamiętać, że punktem wyjścia ścieżki jest folder skryptu, który dokonuje importu;
  • wiersz 4: w celu uruchomienia funkcji [f2] z pliku [module2];

Wyniki są następujące:

1
2
3
4
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/03/main_01.py
f2

Process finished with exit code 0

Wiersz 2, wynik funkcji [f2].

Skrypt [main_02] wygląda następująco:


# import modułu dir1.module2, który zostanie przemianowany
import dir1.module2 as module2
# wykonanie funkcji f2
module2.f2()

W wierszu 2 zmieniono nazwę modułu na [dir1.module], aby uprościć zapis w wierszu 4.

Skrypt [main_03] ma następującą postać:


# import funkcji f2 z modułu dir1.module2
from dir1.module2 import f2
# wykonanie funkcji f2
f2()

Tym razem w wierszu 2 importujemy tylko funkcję [f2], która staje się wówczas funkcją skryptu [main_03] (wiersz 4).

Wszystkie te skrypty działają równie dobrze zarówno w kontekście PyCharm, jak i w konsoli języka Python. Powodem jest to, że w obu przypadkach katalog skryptu wykonywanego, w tym przypadku katalog [03], znajduje się w ścieżce Python Path. Dzięki temu katalog [dir1/module2] zostaje znaleziony.

9.4. Skrypty [import_04]

Image

W tym przypadku foldery [dir1] i [dir2] zostały umieszczone w folderze [Sources Root] projektu PyCharm.

Pierwszym zaimportowanym modułem jest [module3]:


# funkcja
def f3():
    print("f3")

Drugim zaimportowanym modułem jest [module4]:


from module3 import f3

# funkcja
def f4():
    f3()
    print("f4")
  • W wierszu 1 importowana jest funkcja [f3] z pliku [module3]. W tym miejscu widoczna jest funkcja [module3], ponieważ umieszczono jej plik [dir1] w pliku [Sources Root];
  • wiersze 4–6: definiujemy funkcję [f4], która wywołuje funkcję [f3] z pliku [module3];

Główny skrypt [main_01] wygląda następująco:


# importujemy moduł4
from module4 import f4
# wykonanie f4
f4()
  • w wierszu 2 importowany jest moduł [module4]. Jest on widoczny, ponieważ jego folder [dir2] został umieszczony w folderze [Sources Root] modułu PyCharm;
  • w wierszu 4: wykonanie funkcji [f4] z modułu [module4];

Wyniki wykonania funkcji [main_01] w funkcji PyCharm są następujące:

1
2
3
4
5
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/04/main_01.py
f3
f4

Process finished with exit code 0

Teraz uruchommy [main_01] w terminalu (konsoli) języka Python:

Image

Wyniki są następujące:

1
2
3
4
5
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\04>python main_01.py
Traceback (most recent call last):
  File "main_01.py", line 2, in <module>
    from module4 import f4
ModuleNotFoundError: No module named 'module4'

Co się stało? Terminal Pythona nie ma żadnej wiedzy na temat ścieżki Python Path ani skryptów [Sources Root] i PyCharm. Ma on własną ścieżkę Python Path. W tej ścieżce zawsze znajduje się katalog skryptu, który jest uruchamiany, w tym przypadku skryptu [main_01]. Zna więc katalog [import/04]. W uruchomionym skrypcie znajduje wiersz:


from module4 import f4

Interpreter języka Python szuka pliku [module4] w katalogach swojej ścieżki Python Path. Jednak plik [module4] nie znajduje się w pliku [import/04], który wprawdzie znajduje się w ścieżce Python Path, ale w pliku [import/04/dir2], którego tam nie ma. Stąd bierze się błąd.

Mamy więc do czynienia z problemem, z którym już się spotkaliśmy: skrypt działający poprawnie w PyCharm może ulec awarii w środowisku terminala Python. Jest to powtarzający się problem, który będziemy musieli rozwiązać.

9.5. Skrypty [import_05]

Image

Uwaga: foldery [dir1] i [dir2] zostały umieszczone w ścieżce Python Path. Zauważmy już na wstępie, że występuje tu konflikt: pliki [module3] i [module4] będą znajdować się w dwóch miejscach ścieżki Python Path pliku PyCharm:

  • w plikach [import/04/dir1] i [import/05/dir1] dla pliku [module3];
  • w plikach [import/04/dir2] i [import/05/dir2] dla pliku [module4];

Można zatem wyodrębnić pliki [import/04/dir1] i [import/04/dir2] z pliku [Sources Root] w ramach projektu PyCharm. Okazuje się, że w tym przypadku [import/05/dir1] jest kopią [import/04/dir1] (to samo dotyczy [dir2]), więc nie ma tu żadnego problemu. Należy jednak zauważyć, że w samym pliku PyCharm należy zwrócić uwagę na listę folderów zawartych w pliku [Sources Root], aby uniknąć konfliktów.

Skrypt [main_01] przyjmuje następującą postać:


import sys
# modyfikujemy plik sys.path, aby uwzględnić w nim foldery
# zawierające klasy do zaimportowania
sys.path.append(".")
sys.path.append("./dir1")
sys.path.append("./dir2")
# importujemy moduł4
from module4 import f4
# wykonanie f4
f4()

Próbujemy rozwiązać problem ścieżki Python Path. Potrzebujemy takiej, która będzie działać zarówno w PyCharm, jak i w terminalu Python. W tym celu sami to naprawimy.

  • Wiersze 4–6: dodajemy katalogi [., ./dir1, ./dir2] do ścieżki Python Path. Aby to zadziałało, katalog bieżący w momencie wykonywania musi być katalogiem [import/05]. Będzie to miało miejsce w katalogu PyCharm, ale niekoniecznie w terminalu Pythona, jak się przekonamy;
  • wiersz 8: importujemy [module4]. Zgodnie z tym, co właśnie zrobiliśmy, powinien on zostać znaleziony w katalogu [./dir2];

Wykonanie w pliku PyCharm daje następujące wyniki:

1
2
3
4
5
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/05/main_01.py
f3
f4

Process finished with exit code 0

Teraz w terminalu Python:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import\05>python main_01.py
f3
f4

Wiersz 1 – folder wykonania to [import/05].

Teraz cofnijmy się o jeden poziom w drzewie katalogów do [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'
  • Wiersz 2: po uruchomieniu pliku [main_01] nie znajdujemy się już w folderze [import/05], lecz w folderze [import]. A przecież napisaliśmy:

sys.path.append(".")
sys.path.append("./dir1")
sys.path.append("./dir2")

To powoduje dodanie do ścieżki Python Path katalogów [import, import/dir1, import/dir2], co wcale nie jest naszym zamiarem. Należy zauważyć, że dodanie do ścieżki Python Path katalogów, które nie istnieją (import/dir1, import/dir2), nie powoduje błędów.

Zrobiliśmy postęp, ale to nie wystarczy. Do ścieżki Python Path należy dodawać nie ścieżki względne, lecz ścieżki bezwzględne.

Skrypt [main_02] jest odmianą skryptu [main_01], który wykorzystuje plik konfiguracyjny [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"
  ]
}

Wartością klucza [dependencies] jest lista folderów, które należy dodać do ścieżki Python Path. Należy zauważyć, że w tym przypadku podano nazwy bezwzględne, a nie względne.

Skrypt [main_02] wykorzystuje plik [config.json] w następujący sposób:


import codecs
import json
import sys

# plik konfiguracyjny
config_filename="C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/import/05/config.json"
# odczyt pliku konfiguracyjnego JSON
with codecs.open(config_filename, "r", "utf-8") as file:
    config = json.load(file)

# modyfikacja pliku sys.path
for directory in config['dependencies']:
    sys.path.append(directory)

# importujemy moduł4
from module4 import f4
# wykonanie f4
f4()

  • wiersz 6: należy zwrócić uwagę, że użyto bezwzględnej nazwy pliku konfiguracyjnego;
  • wiersze 8–9: plik konfiguracyjny jest odczytywany. Na podstawie jego zawartości tworzony jest słownik [config] (wiersz 9);
  • wiersze 11–13: elementy tablicy [config['dependencies']] są dodawane do ścieżki Python Path. Należy zauważyć, że ponieważ w pliku [config.json] podano bezwzględne nazwy folderów, do ścieżki Python Path dodawane są bezwzględne nazwy;
  • wiersz 16: importowany jest plik [module4]. Powinien on zostać znaleziony, ponieważ plik [dir2] znajduje się teraz w ścieżce Python Path;

Wykonanie daje takie same wyniki jak w przypadku pliku [main_02], z tą różnicą, że skrypt nadal działa, nawet gdy katalogem wykonawczym nie jest już [import/05]:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import>python 05/main_02.py
f3
f4

Wiersz 1: katalogem uruchomieniowym jest [import].

Zrobiliśmy postęp. Zauważyliśmy,

  • że musieliśmy samodzielnie zbudować ścieżkę Python Path;
  • że trzeba w nim umieścić bezwzględne ścieżki do wszystkich folderów zawierających moduły importowane przez aplikację;

Jednak umieszczanie nazw bezwzględnych w skryptach nie jest dobrym rozwiązaniem. Gdy tylko projekt zostanie przeniesiony na inną lokalizację, przestaje działać. Musimy znaleźć inne rozwiązanie.

9.6. Skrypty [import_06]

Image

Uwaga: foldery [06, dir1, dir2] zostały umieszczone w folderach [Sources Root] projektu PyCharm. Foldery [dir1, dir2] są identyczne jak te z poprzednich przykładów.

Plik [config.json] ma następującą treść:


{
  "rootDir": "C:/Data/st-2020/dev/python/cours-2020/v-02/imports/06",
  "relativeDependencies": [
    "dir1",
    "dir2"
  ],
  "absoluteDependencies": [
   ]
}

Wprowadzamy dwa rodzaje ścieżek:

  • ścieżki bezwzględne, wiersze 7–8;
  • ścieżki względne, wiersze 3–6. Są one względne względem katalogu głównego z wiersza 2. Dzięki temu, gdy projekt zostanie przeniesiony w inne miejsce, wystarczy zmodyfikować tylko ten wiersz;

Skrypt [utils.py] wykorzystuje plik [config.json] i tworzy ścieżkę Python Path:


# importy
import codecs
import json
import os
import sys

# konfiguracja aplikacji
def config_app(config_filename: str) -> dict:
    # config_filename: nazwa pliku konfiguracyjnego
    # zezwala się na zgłaszanie wyjątków

    # wykorzystanie pliku konfiguracyjnego
    with codecs.open(config_filename, "r""utf-8"as file:
        config = json.load(file)
    # dodawanie zależności do sys.path
    rootDir = config['rootDir']
    # dodajemy zależności związane z projektem do ścieżki systemowej
    for directory in config['relativeDependencies']:
        # dodaje się zależność na początku ścieżki syspath
        sys.path.insert(0f"{rootDir}/{directory}")
    # dodaje się do niego bezwzględne zależności projektu w ścieżce systemowej
    for directory in config['absoluteDependencies']:
        # dodaje się zależność na początku ścieżki systemowej
        sys.path.insert(0, directory)
    # tworzy się słownik konfiguracji
    return config

# katalogu skryptu wykonywanego
def get_scriptdir():
    return os.path.dirname(os.path.abspath(__file__))
  • wiersz 8: funkcja [config_app] otrzymuje jako parametr nazwę pliku konfiguracyjnego;
  • wiersze 12–14: plik konfiguracyjny jest wykorzystywany do utworzenia słownika [config];
  • wiersz 20: [sys.path] to lista katalogów w ścieżce Python Path;
  • wiersze 17–20: względne zależności z pliku konfiguracyjnego są dodawane do ścieżki Python Path. Są one dodawane na początku tablicy [sys.path], wiersz 20. W rzeczywistości, gdy Python szuka modułu, przeszukuje katalogi z [sys.path] w podanej kolejności. Jednak w tym dokumencie moduły o tych samych nazwach będą znajdować się w różnych katalogach [sys.path]. Umieszczając zależności aplikacji na początku tablicy [sys.path], zapewniamy, że zostaną one przeszukane przed innymi folderami w [sys.path], które mogłyby zawierać moduły o tych samych nazwach;
  • wiersze 21–24: bezwzględne zależności pliku konfiguracyjnego są dodawane do ścieżki Python Path;
  • wiersz 26: zwracana jest konfiguracja aplikacji;
  • wiersze 29–30: funkcja [get_scriptdir] zwraca bezwzględną nazwę katalogu skryptu, który jest aktualnie wykonywany (tego, w którym znajduje się wywołanie funkcji);

Główny skrypt [main] wygląda następująco:


# importy
import sys

from utils import config_app


def affiche_path(msg: str):
    # komunikat
    print(f"{msg}------------------------------")
    # sys.path
    for path in sys.path:
        print(path)


# main -------------
try:
    # sys.path jest skonfigurowany
    affiche_path("avant....")
    config = config_app(f"{get_scriptdir()}/config.json")
    affiche_path("après....")
    # importujemy moduł4
    from module4 import f4
    # wykonanie f4
    f4()
except BaseException as erreur:
    print(f"L'erreur suivante s'est produite : {erreur}")
finally:
    print("done")
  • wiersz 4: importowana jest funkcja [config_app]. Należy zauważyć, że ponieważ funkcje [utils] i [main] znajdują się w tym samym folderze, funkcja [import] działa zawsze. Dzieje się tak, ponieważ folder zawierający skrypt główny jest automatycznie dodawany do ścieżki Python Path;
  • wiersze 7–12: funkcja [affiche_path] wyświetla listę folderów ścieżki Python Path;
  • wiersz 19: konfigurujemy aplikację. Należy zauważyć, że do funkcji [config_app] przekazujemy bezwzględną nazwę pliku konfiguracyjnego. Po tej instrukcji ścieżka Python Path została odbudowana;
  • wiersz 22: importujemy [module4]. Dzięki odbudowie ścieżki Python Path moduł ten zostanie znaleziony;
  • wiersz 24: wywołujemy funkcję [f4];

W kontekście PyCharm wyniki wykonania są następujące:


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

Komentarze

  • wiersze 2–13: ścieżka Python Path dla PyCharm. Znajdują się w niej wszystkie foldery umieszczone w [Sources Root] projektu;
  • wiersze 14–29: ścieżka Python Path utworzona przez funkcję [config_app]. W wierszach 15–16 znajdują się dwie zależności, które dodaliśmy;
  • wiersze 22–27: katalogi systemowe interpretera języka Python, który wykonał skrypt;
  • wiersze 28–29: wykonanie przebiega normalnie;

Teraz przejdźmy do kontekstu, który do tej pory powodował błąd wykonania:

  • otwieramy terminal Pythona;
  • przechodzimy do katalogu innego niż ten, w którym znajduje się wykonywany skrypt;

(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

Tym razem wszystko działa poprawnie (wiersze 20–21). Należy zauważyć, że [sys.path] nie zawiera tych samych folderów, co w przypadku wykonywania skryptu pod PyCharm.

9.7. Skrypty [import_07]

Ulepszamy poprzednie rozwiązanie na dwa sposoby:

  • zastępujemy plik konfiguracyjny [config.json] skryptem [config.py]. Plik jSON stwarza bowiem poważny problem: nie można go opatrzyć komentarzami. Słownik [config.json] można zastąpić słownikiem w języku Python, który ma tę zaletę, że można go opatrzyć komentarzami;
  • używamy modułu widocznego dla wszystkich projektów w języku Python na tym komputerze;

9.7.1. Instalacja modułu o zasięgu obejmującym cały komputer

Image

Powyżej tworzymy folder [packages/myutils] w projekcie PyCharm (nazwy nie mają znaczenia).

Skrypt [myutils.py] wygląda następująco:


# importy
import sys
import os


def set_syspath(absolute_dependencies: list):
    # absolute_dependencies: lista bezwzględnych nazw folderów

    # do ścieżki systemowej dodaje się bezwzględne zależności projektu
    for directory in absolute_dependencies:
        # sprawdzamy, czy katalog istnieje
        existe = os.path.exists(directory) and os.path.isdir(directory)
        if not existe:
            # rzucany jest wyjątek
            raise BaseException(f"[set_syspath] le dossier du Python Path [{directory}] n'existe pas")
        else:
            # dodaje się folder na początek ścieżki syspath
            sys.path.insert(0, directory)
  • wiersze 6–18: funkcja [set_syspath] tworzy ścieżkę Python Path zawierającą listę folderów przekazanych jej jako parametr;
  • wiersze 12–15: sprawdzane jest, czy folder, który ma zostać dodany do ścieżki Python Path, istnieje;

Skrypt [__init.py__] (dwa podkreślenia przed i po nazwie; jest to wymóg) wygląda następująco:


from .myutils import set_syspath

Importujemy funkcję [set_syspath] ze skryptu [myutils]. Notacja [.myutils] oznacza ścieżkę [./myutils], a więc skrypt [myutils] znajdujący się w tym samym folderze co [__init.py]. Można by było użyć notacji [myutils]. Jednak zamierzamy utworzyć moduł [myutils] o zasięgu maszynowym. W związku z tym notacja [from myutils import set_syspath] stałaby się niejednoznaczna. Czy chodzi o zaimportowanie skryptu [myutils] z bieżącego folderu, czy też skryptu [myutils] o zasięgu maszynowym? Notacja [.myutils] eliminuje tę niejednoznaczność.

Skrypt [setup.py] (również w tym przypadku nazwa jest narzucona) ma następującą postać:


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)

W tym skrypcie opisuje się moduł, który ma zostać utworzony. W tym przypadku utworzymy go lokalnie. Jednak ten sam proces stosuje się do tworzenia modułu dystrybuowanego oficjalnie (por. |pypi|). Najważniejsze kwestie to:

  • wiersz 3: nazwa utworzonego modułu;
  • wiersz 4: wersja modułu;
  • wiersz 5: jego opis;
  • wiersze 7–8: autor modułu;

Aby zainstalować ten moduł z zakresem działania na poziomie maszyny, należy postępować w następujący sposób:

Image

Następnie w terminalu Python wpisujemy następujące polecenie: [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

Od tej chwili każdy skrypt na maszynie może importować moduł [myutils], nawet jeśli nie znajduje się on w kodzie projektu.

9.7.2. Skrypt [config.py]

Image

Skrypt [config.py] zapewnia konfigurację aplikacji:


def configure():
    import os

    # absolutna nazwa folderu skryptu konfiguracyjnego
    script_dir = os.path.dirname(os.path.abspath(__file__))
    # absolutne ścieżki do folderów, które mają zostać dodane do syspath
    absolute_dependencies = [
        # foldery lokalne
        f"{script_dir}/dir1",
        f"{script_dir}/dir2",
    ]
    # aktualizacja ścieżki syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # zwracamy konfigurację
    return {}
  • wiersz 1: funkcja [configure] zapewnia konfigurację aplikacji;
  • wiersze 7–10: słownik, który wcześniej znajdował się w pliku [config.json];
  • wiersze 9–10: ponieważ mamy do czynienia ze skryptem, możemy bezpośrednio uzyskać bezwzględne nazwy folderów [dir1, dir2];
  • wiersze 12–14: wykorzystujemy funkcję [set_syspath] z modułu [myutils], który właśnie utworzyliśmy, aby zdefiniować ścieżkę Python Path w konfiguracji;
  • wiersz 20: zwracamy słownik konfiguracji aplikacji. W tym przypadku jest on pusty;

9.7.3. Skrypt [main.py]

Główny skrypt [main] wygląda następująco:


# konfiguracja aplikacji
import config

config = config.configure()

# ścieżka systemowa jest skonfigurowana – można przeprowadzić importy
from module4 import f4

# main -------------
try:
    f4()
except BaseException as erreur:
    print(f"L'erreur suivante s'est produite : {erreur}")
finally:
    print("done")
  • wiersze 2–4: konfigurujemy aplikację za pomocą modułu [config.py]. Jest on dostępny, ponieważ znajduje się w tym samym folderze co skrypt główny. Folder skryptu głównego jest zawsze częścią ścieżki Python Path;
  • gdy dochodzimy do wiersza 6, ścieżka Python Path została skonstruowana z uwzględnieniem folderu modułu [module4]. Można go zatem zaimportować w wierszu 7;
  • wiersze 10–15: pozostaje już tylko wywołać funkcję [f4];

Wyniki wykonania w module PyCharm są następujące:

1
2
3
4
5
6
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/07/main.py
f3
f4
done

Process finished with exit code 0

W terminalu Python, poza folderem głównego skryptu, wyniki są następujące:

1
2
3
4
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\import>python 07/main.py
f3
f4
done

Od tej pory będziemy zawsze postępować w ten sam sposób, aby skonfigurować aplikację:

  • obecność skryptu [config.py] w folderze głównego skryptu. Skrypt ten zawiera funkcję [configure], która ma dwa cele:
    • utworzenie ścieżki Python Path dla aplikacji. W tym celu funkcja [config.py] deklaruje wszystkie foldery zawierające moduły używane przez aplikację i tworzy ścieżkę Python Path z ich nazwami bezwzględnymi;
    • utworzenie słownika [config] zawierającego konfigurację aplikacji;

Stosujemy ten schemat do drugiej wersji ćwiczenia praktycznego. Pamiętamy bowiem, że wersja 1 działała w środowisku PyCharm, ale nie w terminalu Python. Problem wynikał ze ścieżki Python Path.