"Zaklęcie" ifname

"Zaklęcie" ifname
Photo by Artem Maltsev / Unsplash

Zastosowanie if name == "main wg. mnie wymaga dodatkowego wyjaśnienia - zwłaszcza, że pewnie będziecie spotykać się z tą konstrukcją przy pracy w Pythonie.

Zacznijmy od przeanalizowania czemu ta konstrukcja powstała.

Jak działa importowanie

Może Was to nieco zaskoczyć - ale przy użyciu słowa import zawartość importowanego pliku z kodem jest URUCHAMIANA!!!

Załóżmy, że stworzyłem sobie prosty projekt składający się z 2 plików. Pierwszy z nich o nazwie moje_funkcje.py zawiera napisane przeze mnie funkcje, które będę wykorzystywał.

Drugi, o nazwie main.py, to główny plik wykonywalny programu - to właśnie ten plik będę uruchamiał gdy będę chciał skorzystać z tej aplikacji.

Struktura projektu wygląda więc tak:

.
├── main.py
└── moje_funkcje.py

1 directory, 2 files

Zawartość main.py wygląda następująco:

from moje_funkcje import *

print("Wpisz liczby do zsumowania:")
a = int(input("1sza liczba: "))
b = int(input("2ga liczba: "))
print(sumuj_liczby(a, b))
  • czyli nic skomplikowanego. W pierwszej linijce importuję całą zawartość z moje_funkcje.py.

Zerknijmy więc jak wygląda kod w moje_funkcje.py:

def sumuj_liczby(a, b):
    return a + b


print("Poniżej testy funkcji:")
assert sumuj_liczby(2, 3) == 5

Zwróćcie uwagę, że poza samą funkcją sumuj_liczby plik ten zawiera też dodatkowe instrukcje - wyświetlające nam informację o przeprowadzeniu testu i sam test z assert.

Zobaczmy co się stanie gdy uruchomię aplikację poprzez python3 main.py:

Zaraz, zaraz - jak to? Przecież w main.py nie robię żadnych testów?! Jedynie importuję funkcję z moje_funkcje i wykorzystuję ją... Skąd ten komunikat?

Na tym właśnie polega problem - importując uruchamiamy wszystko z tego zewnętrznego pliku. Tym samym, mimo tego, że uruchomiłem main.py to wystartowałem wszystko co znajduje się również w moje_funkcje.py - i to właśnie stamtąd pochodzi ten komunikat.

Jak sobie z tym poradzić?

To, że w pliku z którego coś importujemy są też jakieś instrukcje, które są uruchamiane po bezpośrednim jego wywołaniu to nic niezwykłego. Możemy mieć przecież sytuację gdy budujemy sobie kilka skryptów - w zależności od sytuacji raz uruchamiamy jeden plik, innym drugi. Jednocześnie w obu plikach importujemy sobie jakieś funkcje / klasy od sąsiada - tak, żeby nie kopiować tego kodu wiele razy.

Możemy też umieszczać jakieś instrukcje, które ułatwiają nam debugowanie kodu - tak jak w przykładzie powyżej.

Jak w takim razie możemy umieszczać takie fragmenty w sposób, który nie spowoduje ich wywołania przy importowaniu?

Właśnie do tego służy ta dziwna konstrukcja if name == "main".

Zmodyfikuję oba pliki:

  1. main.py
from moje_funkcje import *

print("\nPlik main.py:")
print(__name__)
  1. moje_funkcje.py
def sumuj_liczby(a, b):
    return a + b


print("\nPlik moje_funkcje:")
print(__name__)

I uruchomię python3 main.py:

Plik moje_funkcje:
moje_funkcje

Plik main.py:
__main__

Zwróćcie uwagę, że zmienna name gdy została wywołana z zaimportowanego pliku (w powyższym przykładzie - moje_funkcje.py) przyjęła jego nazwę (czyli moje_funkcje), podczas gdy wywołana w pliku z którego startujemy aplikację zwróciła nam:

__main__

... i nie, to wynika z tego, że nasz plik nazywa się main.py 😄.

Zawsze jak wywołamy tę zmienną wewnątrz uruchamianego pliku to zwróci nam właśnie:

__main__

, a z kolei gdy odwołamy się do niej w jakimś zaimportowanym kodzie to dostaniemy nazwę zaimportowanego pliku.

Właśnie tę właściwość wykorzystujemy, żeby przeciwdziałać uruchamianiu kodu przy imporcie.

Wprowadźmy więc jeszcze raz zmianę w moje_funkcje.py:

def sumuj_liczby(a, b):
    return a + b


if __name__ == "__main__":
    print("Poniżej testy funkcji:")
    assert sumuj_liczby(2, 3) == 5

Plik main.py zostawiam bez zmian (chociaż dobrą praktyką byłoby też dodanie do niego tej konstrukcji z `if name` ).

Po uruchomieniu python3 main.py dostaję:

Trochę "magicznych" zabiegów - i problem z importami rozwiązany.