Wyjaśnić, kiedy plik .py działa jako moduł importowany, a kiedy jako skrypt uruchamiany bezpośrednio. Zrozumieć rolę atrybutu __name__ i konsekwencje efektów ubocznych przy imporcie.
Każdy plik .py jest modułem. Różnica dotyczy sposobu uruchomienia:
python file.py→__name__ == "__main__",import file→__name__ == "file".
Dzięki temu można oddzielić:
- API (funkcje do importu),
- kod uruchamiany interaktywnie lub z CLI.
__name__ to wbudowany atrybut każdego modułu Pythona. Interpreter nadaje mu wartość automatycznie:
| Sposób uruchomienia | Wartość __name__ |
|---|---|
python moj_plik.py |
"__main__" |
python -m moj_pakiet.moj_plik |
"__main__" |
import moj_plik (z innego modułu) |
"moj_plik" |
from moj_pakiet import moj_plik |
"moj_pakiet.moj_plik" |
Wartość __name__ możemy sprawdzić w dowolnym miejscu modułu:
# sprawdz_name.py
print(f"Wartość __name__ w tym pliku: {__name__!r}")Uruchomienie bezpośrednie:
$ python sprawdz_name.py
Wartość __name__ w tym pliku: '__main__'
Import z innego pliku:
>>> import sprawdz_name
Wartość __name__ w tym pliku: 'sprawdz_name'Gdy Python napotka instrukcję import modul, wykonuje następujące kroki:
- Sprawdza cache — czy
modulistnieje już wsys.modules. Jeśli tak, zwraca wcześniej załadowany obiekt. - Szuka pliku — przeszukuje ścieżki z
sys.path(szczegóły w sekcji 05). - Wykonuje cały kod modułu — od góry do dołu, raz (przy pierwszym imporcie).
- Tworzy obiekt modułu — i wstawia go do
sys.modules. - Wiąże nazwę — w bieżącej przestrzeni nazw:
modulwskazuje na obiekt modułu.
Kluczowy punkt: krok 3 oznacza, że każda instrukcja na najwyższym poziomie modułu zostanie wykonana podczas importu. To jest źródło efektów ubocznych.
Diagram: diagrams/module_vs_script.png
Efekt uboczny (ang. side effect) to każda akcja, która zmienia stan programu poza zwracaniem wartości: wypisanie tekstu, otwarcie pliku, modyfikacja zmiennej globalnej.
# zlecenie.py — ZŁY STYL
import datetime
print("Moduł zlecenie załadowany o", datetime.datetime.now())
baza_danych = [] # tworzy się przy imporcie!
def dodaj_zlecenie(nazwa: str) -> None:
baza_danych.append(nazwa)
print(f"Dodano: {nazwa}")
dodaj_zlecenie("testowe") # wykonuje się przy imporcie!# main.py
import zlecenie # wypisze tekst i doda "testowe" do bazy — NIESPODZIANKA!
zlecenie.dodaj_zlecenie("prawdziwe")
print(zlecenie.baza_danych)
# Wynik: ['testowe', 'prawdziwe'] — element 'testowe' dodany nieoczekiwanie# zlecenie.py — DOBRY STYL
import datetime
baza_danych: list[str] = []
def dodaj_zlecenie(nazwa: str) -> None:
baza_danych.append(nazwa)
print(f"Dodano: {nazwa}")
if __name__ == "__main__":
# Ten blok NIE wykona się przy imporcie
print("Moduł zlecenie załadowany o", datetime.datetime.now())
dodaj_zlecenie("testowe")Teraz import zlecenie importuje tylko funkcje i zmienną — bez efektów ubocznych.
Plik: examples/calc_module.py
def add(a: float, b: float) -> float:
return a + b
def multiply(a: float, b: float) -> float:
return a * b
def main() -> None:
print("2 + 3 =", add(2, 3))
if __name__ == "__main__":
main()Dlaczego to jest dobre podejście:
- importując moduł w innym pliku, dostajesz tylko funkcje (
add,multiply), - kod demonstracyjny
main()nie uruchamia się „sam" przy imporcie, - testowanie jest prostsze, bo funkcje są czyste i niezależne od CLI.
Plik: examples/run_as_module.py importuje add i uruchamia prosty scenariusz.
To symuluje sytuację, gdy masz wiele modułów i jeden punkt wejścia programu.
Python umożliwia uruchamianie modułu jako skryptu za pomocą flagi -m:
python -m pakiet.modulRóżnica w stosunku do python pakiet/modul.py:
-mkorzysta z systemu importów (szuka modułu nasys.path),- poprawnie rozwiązuje importy wewnątrz pakietu,
- uruchamia plik
__main__.pypakietu, jeśli istnieje.
Przykład — uruchamianie modułu z pakietu standardowego:
python -m json.tool dane.json # ładne formatowanie JSON
python -m http.server 8000 # prosty serwer HTTP
python -m venv .venv # tworzenie środowiska wirtualnegoDla własnych pakietów: jeśli w katalogu pakietu umieścisz plik __main__.py, to polecenie python -m moj_pakiet uruchomi właśnie ten plik.
moj_pakiet/
__init__.py
logika.py
__main__.py ← punkt wejścia przy `python -m moj_pakiet`
# moj_pakiet/__main__.py
from .logika import oblicz
def main() -> None:
wynik = oblicz(10)
print(f"Wynik: {wynik}")
if __name__ == "__main__":
main()python src/_03-modules/02-module-vs-script/examples/calc_module.py
python src/_03-modules/02-module-vs-script/examples/run_as_module.py- odróżnić kod biblioteczny od kodu uruchamialnego,
- utrwalić znaczenie
__name__, - sprawdzić wpływ na testowalność.
- Dodaj funkcję
subtract(a, b)doexamples/calc_module.py. - Użyj jej w
main(). - W osobnym pliku zaimportuj
subtracti wywołaj ją bez uruchamianiamain(). - Sprawdź, że import nie uruchamia kodu demonstracyjnego.
- Student umie zaprojektować moduł, który jest jednocześnie wygodny do importu i uruchamiania.
- Dodaj
argparsewmain()i obsłuż argumenty z wiersza poleceń.
exercises/tasks.py— klasyfikacja trybu uruchomienia,exercises/solutions_module_vs_script.py— referencyjna implementacja,exercises/test_solutions.py— testy.
- umieszczanie całej logiki programu w bloku
if __name__ == "__main__":(zamiast w oddzielnych funkcjach), - importowanie z plików, które mają efekty uboczne już przy imporcie (np.
print, zapis do pliku), - mieszanie kodu bibliotecznego i kodu prezentacyjnego w jednej funkcji,
- zapominanie, że
__name__zmienia wartość w zależności od kontekstu uruchomienia.
- Co dokładnie oznacza wartość
__name__podczas importu? - Dlaczego blok
if __name__ == "__main__":poprawia testowalność? - Kiedy warto wydzielić oddzielny plik „runner"?
- Czym różni się
python plik.pyodpython -m modul? - Co to jest efekt uboczny importu i jak go uniknąć?
- Jak działa
__main__.pyw pakiecie?
