Wykonanie kodu Pythona z opcją -m lub bez

111

Interpreter Pythona ma opcję -m modułu „Uruchamia moduł modułu biblioteki jako skrypt”.

Za pomocą tego kodu Pythona a.py:

if __name__ == "__main__":
    print __package__
    print __name__

Testowałem, python -m aaby uzyskać

"" <-- Empty String
__main__

podczas gdy python a.pywraca

None <-- None
__main__

Dla mnie te dwa wywołania wydają się być takie same, z wyjątkiem tego, że __package__ nie jest None, gdy są wywoływane z opcją -m.

Co ciekawe, z python -m runpy aotrzymuję to samo, co w python -m aprzypadku modułu pythona skompilowanego w celu uzyskania a.pyc.

Jaka jest (praktyczna) różnica między tymi inwokacjami? Jakieś wady i zalety między nimi?

Również dokumentacja Python Essential Reference Davida Beazleya wyjaśnia to jako „ Opcja -m uruchamia moduł biblioteki jako skrypt, który jest wykonywany wewnątrz modułu __main__ przed wykonaniem głównego skryptu ”. Co to znaczy?

prosseek
źródło

Odpowiedzi:

169

Gdy używasz -mflagi wiersza poleceń , Python zaimportuje moduł lub pakiet , a następnie uruchomi go jako skrypt. Jeśli nie używasz -mflagi, nazwany plik jest uruchamiany jako zwykły skrypt .

To rozróżnienie jest ważne, gdy próbujesz uruchomić pakiet. Istnieje duża różnica między:

python foo/bar/baz.py

i

python -m foo.bar.baz

tak jak w drugim przypadku, foo.barjest importowany, a import względny będzie działał poprawnie z foo.barpunktem wyjścia.

Próbny:

$ mkdir -p test/foo/bar
$ touch test/foo/__init__.py
$ touch test/foo/bar/__init__.py
$ cat << EOF > test/foo/bar/baz.py 
> if __name__ == "__main__":
>     print __package__
>     print __name__
> 
> EOF
$ PYTHONPATH=test python test/foo/bar/baz.py 
None
__main__
$ PYTHONPATH=test python -m foo.bar.baz 
foo.bar
__main__

W rezultacie Python musi faktycznie dbać o pakiety podczas korzystania z -mprzełącznika. Zwykły skrypt nigdy nie może być pakietem, więc __package__jest ustawiony na None.

Ale uruchom pakiet lub moduł wewnątrz pakietu z, -ma teraz istnieje przynajmniej możliwość pakietu, więc __package__zmienna jest ustawiana na wartość ciągu; w powyższej demonstracji jest ustawiony na foo.bar, dla zwykłych modułów nie znajdujących się w pakiecie, jest ustawiony na pusty łańcuch.

Jeśli chodzi o __main__ moduł ; Python importuje uruchamiane skrypty tak, jak zwykły moduł. Tworzony jest nowy obiekt modułu do przechowywania globalnej przestrzeni nazw, przechowywanej w sys.modules['__main__']. Do tego właśnie __name__odnosi się zmienna, jest to klucz w tej strukturze.

W przypadku pakietów można utworzyć __main__.pymoduł i uruchomić go podczas działania python -m package_name; w rzeczywistości jest to jedyny sposób, w jaki można uruchomić pakiet jako skrypt:

$ PYTHONPATH=test python -m foo.bar
python: No module named foo.bar.__main__; 'foo.bar' is a package and cannot be directly executed
$ cp test/foo/bar/baz.py test/foo/bar/__main__.py
$ PYTHONPATH=test python -m foo.bar
foo.bar
__main__

Dlatego podczas nadawania nazwy pakietowi do uruchomienia -m, Python szuka __main__modułu zawartego w tym pakiecie i wykonuje go jako skrypt. Jego nazwa jest wtedy nadal ustawiona na __main__, a obiekt modułu jest nadal przechowywany w sys.modules['__main__'].

Martijn Pieters
źródło
1
Co właściwie oznacza polecenie PYTHONPATH=test python -m foo.bar? Czy mógłbyś to szczegółowo wyjaśnić?
Andriy
3
@Andriy: PYTHONPATHustawia zmienną środowiskową; rozszerza serię katalogów, w których Python będzie szukał modułów podczas importu; tutaj dodaje testkatalog do tej serii. Umieszczając go w tej samej linii poleceń, ma zastosowanie tylko do tego pojedynczego pythonpolecenia. -mmówi Pythonowi, aby zaimportował określony moduł, tak jakbyś uruchomił import foo.bar. Jednak Python automatycznie uruchomi __main__moduł w pakiecie jako skrypt, gdy użyjesz tego przełącznika.
Martijn Pieters
1
having to use -m always is not that user-.friendly.Myślę, że używanie i nie używanie miksu -mjest mniej przyjazne dla użytkownika.
Simin Jie
1
@SiminJie: skrypty można otwierać w dowolnej ścieżce, a następnie ich katalog nadrzędny jest dodawany do ścieżki wyszukiwania modułu. -mdziała tylko dla bieżącego katalogu lub katalogów już zarejestrowanych w ścieżce wyszukiwania. To był mój punkt widzenia. -mnie jest czymś, co dajesz użytkownikom końcowym w związku z tą samą kwestią użyteczności.
Martijn Pieters
1
@ flow2k: Mam na myśli, że from Photos import ...będzie narzekać. Tak by było import Photos.<something>. import Photosdziała tylko dlatego, że Python obsługuje pakiety w przestrzeni nazw (gdzie dwie oddzielne dystrybucje zapewniają Photos.fooi Photos.baroddzielnie i mogą być niezależnie zarządzane).
Martijn Pieters
25

Wykonanie kodu Pythona z opcją -m lub bez

Użyj -mflagi.

Wyniki są prawie takie same, gdy masz skrypt, ale kiedy tworzysz pakiet bez -mflagi, nie ma sposobu, aby import działał poprawnie, jeśli chcesz uruchomić podpakiet lub moduł w pakiecie jako główny wpis wskaż swój program (i uwierz mi, próbowałem).

Dokumenty

Podobnie jak w dokumentach pod flagą -m :

Wyszukaj w sys.path nazwany moduł i wykonaj jego zawartość jako __main__moduł.

i

Podobnie jak w przypadku opcji -c, bieżący katalog zostanie dodany na początku sys.path.

więc

python -m pdb

jest w przybliżeniu odpowiednikiem

python /usr/lib/python3.5/pdb.py

(zakładając, że nie masz pakietu lub skryptu w swoim bieżącym katalogu o nazwie pdb.py)

Wyjaśnienie:

Zachowanie jest „celowo podobne do” skryptów.

Wiele modułów bibliotek standardowych zawiera kod, który jest wywoływany podczas ich wykonywania jako skrypt. Przykładem jest moduł timeit:

Część kodu w Pythonie ma być uruchamiana jako moduł: (myślę, że ten przykład jest lepszy niż przykład w dokumencie z opcją wiersza poleceń)

$ python -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 3: 40.3 usec per loop
$ python -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 3: 33.4 usec per loop
$ python -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 3: 25.2 usec per loop

A z najważniejszych informacji o wydaniu dla Pythona 2.4 :

Opcja wiersza poleceń -m - python -m nazwa_modułu znajdzie moduł w bibliotece standardowej i wywoła go. Na przykład python -m pdb jest równoważne zpython /usr/lib/python2.4/pdb.py

Pytanie uzupełniające

Również dokumentacja Python Essential Reference Davida Beazleya wyjaśnia to jako „Opcja -m uruchamia moduł biblioteki jako skrypt, który jest wykonywany wewnątrz __main__modułu przed wykonaniem głównego skryptu”.

Oznacza to, że każdy moduł, który można wyszukać za pomocą instrukcji importu, może zostać uruchomiony jako punkt wejścia programu - jeśli ma blok kodu, zwykle pod koniec, z if __name__ == '__main__':.

-m bez dodawania bieżącego katalogu do ścieżki:

Komentarz tutaj w innym miejscu mówi:

To, że opcja -m dodaje również bieżący katalog do sys.path, jest oczywiście kwestią bezpieczeństwa (patrz: atak wstępnego ładowania). To zachowanie jest podobne do kolejności wyszukiwania w bibliotekach w systemie Windows (zanim została ostatnio ulepszona). Szkoda, że ​​Python nie podąża za trendem i nie oferuje prostego sposobu na wyłączenie dodawania. do sys.path

Cóż, to pokazuje możliwy problem - (w Windows usuń cudzysłowy):

echo "import sys; print(sys.version)" > pdb.py

python -m pdb
3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul  5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)]

Użyj -Iflagi, aby zablokować to w środowiskach produkcyjnych (nowość w wersji 3.4):

python -Im pdb
usage: pdb.py [-c command] ... pyfile [arg] ...
etc...

z dokumentów :

-I

Uruchom Pythona w trybie izolowanym. To także implikuje -E i -s. W trybie izolowanym sys.path nie zawiera ani katalogu skryptu, ani katalogu pakietów witryn użytkownika. Wszystkie zmienne środowiskowe PYTHON * są również ignorowane. Mogą zostać nałożone dalsze ograniczenia, aby uniemożliwić użytkownikowi wstrzyknięcie złośliwego kodu.

Co robi __package__?

Umożliwia to jawne importowanie względne, jednak niezbyt związane z tym pytaniem - zobacz odpowiedź tutaj: Jaki jest cel atrybutu „__package__” w Pythonie?

Aaron Hall
źródło
Która ścieżka jest dodawana do sys.path, gdy używany jest przełącznik -m?
zmienna
Mam to już zacytowane: „Podobnie jak w przypadku opcji -c, bieżący katalog zostanie dodany na początku sys.path”. ale wyjaśniłem, do czego odnosi się ten cytat.
Aaron Hall
Chodzi mi o to, że - przypuśćmy, że w katalogu D: \ test uruchomię polecenie - python -m foo.bar.boo to doda folder instalacyjny pythona lub katalog D: \ test do sys.path? Rozumiem, że doda d: \ test do sys.path, zaimportuje foo.bar i uruchomi skrypt boo
zmienna
@variable - tak, spróbuj.
Aaron Hall
1

Głównym powodem uruchamiania modułu (lub pakietu) jako skryptu z opcją -m jest uproszczenie wdrażania, szczególnie w systemie Windows. Możesz zainstalować skrypty w tym samym miejscu w bibliotece Pythona, w którym zwykle znajdują się moduły - zamiast zanieczyszczać PATH lub globalne katalogi wykonywalne, takie jak ~ / .local (katalog skryptów dla użytkownika jest śmiesznie trudny do znalezienia w systemie Windows).

Następnie po prostu wpisujesz -m, a Python automatycznie odnajduje skrypt. Na przykład python -m pipznajdzie poprawny pip dla tej samej instancji interpretera języka Python, który go wykonuje. Bez -m, jeśli użytkownik ma zainstalowanych kilka wersji Pythona, która z nich byłaby „globalnym” pip?

Jeśli użytkownik woli „klasyczne” punkty wejścia dla skryptów wiersza poleceń, można je łatwo dodać jako małe skrypty gdzieś w PATH lub pip może je utworzyć w czasie instalacji za pomocą parametru entry_points w pliku setup.py.

Po prostu sprawdź __name__ == '__main__'i zignoruj ​​inne niewiarygodne szczegóły implementacji.

ddbug
źródło
To, że opcja -m dodaje również bieżący katalog do sys.path, jest oczywiście kwestią bezpieczeństwa (patrz: atak wstępnego ładowania ). To zachowanie jest podobne do kolejności wyszukiwania w bibliotekach w systemie Windows (zanim została ostatnio ulepszona). Szkoda, że ​​Python nie podąża za trendem i nie oferuje prostego sposobu na wyłączenie dodawania. do sys.path.
ddbug