Ausführung von Python-Code mit der Option -m oder nicht

110

Das Python - Interpreter hat -m Modul Option , dass „Läuft Bibliotheksmodul - Modul als Skript“.

Mit diesem Python-Code a.py:

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

Ich habe getestet python -m a, um zu bekommen

"" <-- Empty String
__main__

während python a.pykehrt zurück

None <-- None
__main__

Für mich scheinen diese beiden Aufrufe identisch zu sein, außer dass __package__ nicht None ist, wenn sie mit der Option -m aufgerufen werden.

Interessanterweise python -m runpy aerhalte ich mit das gleiche wie python -m amit dem Python-Modul, das für a.pyc kompiliert wurde.

Was ist der (praktische) Unterschied zwischen diesen Aufrufen? Irgendwelche Vor- und Nachteile zwischen ihnen?

In der Python Essential Reference von David Beazley wird dies außerdem als " Die Option -m führt ein Bibliotheksmodul als Skript aus, das vor der Ausführung des Hauptskripts im Modul __main__ ausgeführt wird ". Was heißt das?

prosseek
quelle

Antworten:

168

Wenn Sie das -mBefehlszeilenflag verwenden , importiert Python ein Modul oder Paket für Sie und führt es dann als Skript aus. Wenn Sie das -mFlag nicht verwenden , wird die von Ihnen genannte Datei nur als Skript ausgeführt .

Die Unterscheidung ist wichtig, wenn Sie versuchen, ein Paket auszuführen. Es gibt einen großen Unterschied zwischen:

python foo/bar/baz.py

und

python -m foo.bar.baz

wie im letzteren Fall foo.barwird importiert und relative Importe funktionieren korrekt foo.barals Ausgangspunkt.

Demo:

$ 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__

Infolgedessen muss sich Python bei der Verwendung des -mSwitches tatsächlich um Pakete kümmern . Ein normales Skript kann nie sein ein Paket, so __package__eingestellt ist None.

Führen Sie jedoch ein Paket oder Modul in einem Paket mit aus, -mund jetzt besteht zumindest die Möglichkeit eines Pakets, sodass die __package__Variable auf einen Zeichenfolgenwert festgelegt wird. In der obigen Demonstration wird festgelegt foo.bar, dass für einfache Module, die sich nicht in einem Paket befinden, eine leere Zeichenfolge festgelegt wird.

Wie für das __main__ Modul ; Python importiert Skripte, die wie ein reguläres Modul ausgeführt werden. Ein neues Modulobjekt wird erstellt, um den globalen Namespace zu speichern, der in gespeichert ist sys.modules['__main__']. Darauf __name__bezieht sich die Variable, sie ist ein Schlüssel in dieser Struktur.

Für Pakete können Sie ein __main__.pyModul erstellen und dieses beim Ausführen ausführen lassen python -m package_name. in der Tat , das ist der einzige Weg , Sie können ein Paket als Script ausführen:

$ 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__

Wenn -mPython ein Paket zum Ausführen benennt , sucht es nach einem __main__in diesem Paket enthaltenen Modul und führt dieses als Skript aus. Der Name wird dann weiterhin festgelegt __main__und das Modulobjekt wird weiterhin gespeichert sys.modules['__main__'].

Martijn Pieters
quelle
1
Was bedeutet eigentlich Befehl PYTHONPATH=test python -m foo.bar? Könnten Sie es bitte im Detail erklären?
Andriy
3
@Andriy: Setzt PYTHONPATHeine Umgebungsvariable; Es erweitert die Reihe von Verzeichnissen, in denen Python beim Importieren nach Modulen sucht. Hier wird das testVerzeichnis zu dieser Serie hinzugefügt. Wenn Sie es in dieselbe Befehlszeile einfügen, gilt es nur für diesen einzelnen pythonBefehl. -mWeist Python an, ein bestimmtes Modul zu importieren, als ob Sie es ausgeführt hätten import foo.bar. Python führt jedoch automatisch ein __main__Modul in einem Paket als Skript aus, wenn Sie diesen Schalter verwenden.
Martijn Pieters
1
having to use -m always is not that user-.friendly.Ich denke, Mixen mit und Nicht-Verwenden -mist weniger benutzerfreundlich.
Simin Jie
1
@SiminJie: Skripte können in einem beliebigen Pfad geöffnet werden, und das übergeordnete Verzeichnis wird dem Modul-Suchpfad hinzugefügt. -mFunktioniert nur für das aktuelle Verzeichnis oder die Verzeichnisse, die bereits im Suchpfad registriert sind. Das war mein Punkt. -mist nichts, was Sie Endbenutzern für dieses Usability-Problem geben.
Martijn Pieters
1
@ flow2k: Ich meine, das from Photos import ...wird sich beschweren. Würde auch import Photos.<something>. import Photosfunktioniert nur, weil Python Namespace-Pakete unterstützt (wobei zwei separate Distributionen Photos.foound Photos.barseparat bereitgestellt werden und diese unabhängig verwaltet werden können).
Martijn Pieters
24

Ausführung von Python-Code mit der Option -m oder nicht

Verwenden Sie die -mFlagge.

Die Ergebnisse sind nahezu identisch, wenn Sie ein Skript haben. Wenn Sie jedoch ein Paket ohne -mFlag entwickeln, können die Importe nicht ordnungsgemäß ausgeführt werden, wenn Sie ein Unterpaket oder Modul im Paket als Haupteintrag ausführen möchten Zeigen Sie auf Ihr Programm (und glauben Sie mir, ich habe es versucht.)

Die Dokumente

Wie die Dokumente auf der Flagge -m sagen:

Durchsuchen Sie sys.path nach dem benannten Modul und führen Sie dessen Inhalt als __main__Modul aus.

und

Wie bei der Option -c wird das aktuelle Verzeichnis am Anfang von sys.path hinzugefügt.

so

python -m pdb

ist ungefähr gleichbedeutend mit

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

(vorausgesetzt, Sie haben kein Paket oder Skript in Ihrem aktuellen Verzeichnis namens pdb.py)

Erläuterung:

Das Verhalten wird "absichtlich ähnlich" zu Skripten gemacht.

Viele Standardbibliotheksmodule enthalten Code, der bei ihrer Ausführung als Skript aufgerufen wird. Ein Beispiel ist das Timeit-Modul:

Einige Python-Codes sollen als Modul ausgeführt werden: (Ich denke, dieses Beispiel ist besser als das Beispiel für die Befehlszeilenoption doc)

$ 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

Und aus den Highlights der Versionshinweise für Python 2.4 :

Die Befehlszeilenoption -m - python -m modulename findet ein Modul in der Standardbibliothek und ruft es auf. Zum Beispiel python -m pdb ist äquivalent zupython /usr/lib/python2.4/pdb.py

Zusatzfrage

In der Python Essential Reference von David Beazley wird dies außerdem als "Die Option -m führt ein Bibliotheksmodul als Skript aus, das __main__vor der Ausführung des Hauptskripts innerhalb des Moduls ausgeführt wird".

Dies bedeutet, dass jedes Modul, das Sie mit einer Importanweisung nachschlagen können, als Einstiegspunkt des Programms ausgeführt werden kann - wenn es einen Codeblock hat, normalerweise gegen Ende, mit if __name__ == '__main__':.

-m ohne das aktuelle Verzeichnis zum Pfad hinzuzufügen:

Ein Kommentar hier an anderer Stelle sagt:

Dass die Option -m auch das aktuelle Verzeichnis zu sys.path hinzufügt, ist offensichtlich ein Sicherheitsproblem (siehe: Preload-Angriff). Dieses Verhalten ähnelt der Suchreihenfolge für Bibliotheken in Windows (bevor es kürzlich gehärtet wurde). Es ist schade, dass Python nicht dem Trend folgt und keine einfache Möglichkeit bietet, das Hinzufügen zu deaktivieren. zu sys.path

Nun, dies zeigt das mögliche Problem - (in Windows entfernen Sie die Anführungszeichen):

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)]

Verwenden Sie das -IFlag, um dies für Produktionsumgebungen zu sperren (neu in Version 3.4):

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

aus den Dokumenten :

-I

Führen Sie Python im isolierten Modus aus. Dies impliziert auch -E und -s. Im isolierten Modus enthält sys.path weder das Verzeichnis des Skripts noch das Site-Packages-Verzeichnis des Benutzers. Alle PYTHON * -Umgebungsvariablen werden ebenfalls ignoriert. Weitere Einschränkungen können auferlegt werden, um zu verhindern, dass der Benutzer schädlichen Code einfügt.

Was macht __package__das

Es ermöglicht explizite relative Importe, die für diese Frage jedoch nicht besonders wichtig sind - siehe diese Antwort hier: Was ist der Zweck des Attributs "__package__" in Python?

Aaron Hall
quelle
Welcher Pfad wird dem sys.path hinzugefügt, wenn der Schalter -m verwendet wird?
Variable
Ich habe bereits zitiert: "Wie bei der Option -c wird das aktuelle Verzeichnis am Anfang von sys.path hinzugefügt." aber ich habe klargestellt, worauf sich das Zitat bezieht.
Aaron Hall
Ich meine, dass - angenommen, ich führe im Verzeichnis D: \ test den Befehl - python -m foo.bar.boo aus. Fügt dies dann den Python-Installationsordner oder das Verzeichnis D: \ test zu sys.path hinzu? Mein Verständnis ist, dass es d: \ test zu sys.path hinzufügen, foo.bar importieren und boo script
variable
@variable - ja, probier es aus.
Aaron Hall
1

Der Hauptgrund, ein Modul (oder Paket) als Skript mit -m auszuführen, besteht in der Vereinfachung der Bereitstellung, insbesondere unter Windows. Sie können Skripte an derselben Stelle in der Python-Bibliothek installieren, an der sich die Module normalerweise befinden - anstatt PATH oder globale ausführbare Verzeichnisse wie ~ / .local zu verschmutzen (das Skriptverzeichnis pro Benutzer ist in Windows lächerlich schwer zu finden).

Dann geben Sie einfach -m ein und Python findet das Skript automatisch. python -m pipFinden Sie beispielsweise den richtigen Pip für dieselbe Instanz des Python-Interpreters, der ihn ausführt. Ohne -m, wenn der Benutzer mehrere Python-Versionen installiert hat, welche wäre die "globale" Pip?

Wenn der Benutzer "klassische" Einstiegspunkte für Befehlszeilenskripte bevorzugt, können diese einfach als kleine Skripte irgendwo in PATH hinzugefügt werden, oder pip kann diese bei der Installation mit dem Parameter entry_points in setup.py erstellen.

Suchen Sie einfach nach __name__ == '__main__'anderen nicht zuverlässigen Implementierungsdetails und ignorieren Sie diese.

ddbug
quelle
Dass die Option -m auch das aktuelle Verzeichnis zu sys.path hinzufügt, ist offensichtlich ein Sicherheitsproblem (siehe: Angriff vor dem Laden ). Dieses Verhalten ähnelt der Suchreihenfolge für Bibliotheken in Windows (bevor es kürzlich gehärtet wurde). Es ist schade, dass Python nicht dem Trend folgt und keine einfache Möglichkeit bietet, das Hinzufügen zu deaktivieren. zu sys.path.
DDBUG