Exécution de code Python avec l'option -m ou non

111

L'interpréteur python a l' option de -m module qui "Exécute le module de module de bibliothèque en tant que script".

Avec ce code python a.py:

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

J'ai testé python -m apour obtenir

"" <-- Empty String
__main__

alors que python a.pyretourne

None <-- None
__main__

Pour moi, ces deux appels semblent être les mêmes sauf que __package__ n'est pas None lorsqu'il est appelé avec l'option -m.

Fait intéressant, avec python -m runpy a, j'obtiens la même chose python -m aqu'avec le module python compilé pour obtenir a.pyc.

Quelle est la différence (pratique) entre ces invocations? Des avantages et des inconvénients entre eux?

De plus, le Python Essential Reference de David Beazley l'explique comme suit: « L'option -m exécute un module de bibliothèque comme un script qui s'exécute à l'intérieur du module __main__ avant l'exécution du script principal ». Qu'est-ce que ça veut dire?

prosseek
la source

Réponses:

169

Lorsque vous utilisez l' -mindicateur de ligne de commande , Python importera un module ou un package pour vous, puis l'exécutera en tant que script. Lorsque vous n'utilisez pas l' -mindicateur, le fichier que vous avez nommé est exécuté comme un simple script .

La distinction est importante lorsque vous essayez d'exécuter un package. Il y a une grande différence entre:

python foo/bar/baz.py

et

python -m foo.bar.baz

comme dans ce dernier cas, foo.barest importé et les importations relatives fonctionneront correctement avec foo.barcomme point de départ.

Démo:

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

En conséquence, Python doit réellement se soucier des packages lors de l'utilisation du -mcommutateur. Un script normal ne peut jamais être un package, il __package__est donc défini sur None.

Mais exécutez un package ou un module à l' intérieur d' un package avec -met maintenant il y a au moins la possibilité d'un package, donc la __package__variable est définie sur une valeur de chaîne; dans la démonstration ci-dessus, il est défini sur foo.bar, pour les modules simples ne figurant pas dans un package, il est défini sur une chaîne vide.

Quant au __main__ module ; Python importe les scripts en cours d'exécution comme un module normal. Un nouvel objet module est créé pour contenir l'espace de noms global, stocké dans sys.modules['__main__']. C'est à cela que se __name__réfère la variable, c'est une clé dans cette structure.

Pour les packages, vous pouvez créer un __main__.pymodule et l'exécuter lors de l'exécution python -m package_name; en fait , qui est la seule façon que vous pouvez exécuter un package en tant que script:

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

Ainsi, lors de la désignation d'un package à exécuter avec -m, Python recherche un __main__module contenu dans ce package et l'exécute en tant que script. Son nom est alors toujours défini sur __main__, et l'objet module est toujours stocké dans sys.modules['__main__'].

Martijn Pieters
la source
1
Que PYTHONPATH=test python -m foo.barsignifie réellement la commande ? Pourriez-vous l'expliquer en détail, s'il vous plaît?
Andriy
3
@Andriy: PYTHONPATHdéfinit une variable d'environnement; il étend la série de répertoires où Python recherchera les modules lors de l'importation; ici, il ajoute le testrépertoire à cette série. En le plaçant sur la même ligne de commande, il ne s'applique qu'à cette seule pythoncommande. -mdit à Python d'importer un module spécifique, comme si vous exécutiez import foo.bar. Cependant, Python exécutera automatiquement un __main__module dans un package en tant que script lorsque vous utilisez ce commutateur.
Martijn Pieters
1
having to use -m always is not that user-.friendly.Je pense que le fait d'utiliser et de ne pas utiliser -mest moins convivial.
Simin Jie
1
@SiminJie: les scripts peuvent être ouverts dans n'importe quel chemin arbitraire , puis leur répertoire parent est ajouté au chemin de recherche du module. -mne fonctionne que pour le répertoire courant ou les répertoires déjà enregistrés sur le chemin de recherche. C'était mon point. -mn'est pas quelque chose que vous donnez aux utilisateurs finaux pour ce problème de convivialité.
Martijn Pieters
1
@ flow2k: Je veux dire que ça from Photos import ...va se plaindre. Il en serait de même import Photos.<something>. import Photosne fonctionne que parce que Python prend en charge les packages d'espacement de noms (où deux distributions distinctes fournissent Photos.fooet Photos.barséparément et peuvent être gérées indépendamment).
Martijn Pieters
25

Exécution de code Python avec l'option -m ou non

Utilisez le -mdrapeau.

Les résultats sont à peu près les mêmes lorsque vous avez un script, mais lorsque vous développez un package, sans l' -mindicateur, il n'y a aucun moyen de faire fonctionner correctement les importations si vous souhaitez exécuter un sous-package ou un module dans le package en tant qu'entrée principale montrez votre programme (et croyez-moi, j'ai essayé.)

Les docs

Comme les documents sur l'indicateur -m disent:

Recherchez dans sys.path le module nommé et exécutez son contenu en tant que __main__module.

et

Comme avec l'option -c, le répertoire courant sera ajouté au début de sys.path.

alors

python -m pdb

équivaut à peu près à

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

(en supposant que vous n'avez pas de package ou de script dans votre répertoire actuel appelé pdb.py)

Explication:

Le comportement est rendu «délibérément similaire aux» scripts.

De nombreux modules de bibliothèque standard contiennent du code qui est appelé lors de leur exécution en tant que script. Un exemple est le module timeit:

Un certain code python est destiné à être exécuté en tant que module: (Je pense que cet exemple est meilleur que l'exemple de doc d'option de ligne de commande)

$ 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

Et d'après les faits saillants de la note de publication pour Python 2.4 :

L'option de ligne de commande -m - python -m nom_module trouvera un module dans la bibliothèque standard et l'invoquera. Par exemple, python -m pdb équivaut àpython /usr/lib/python2.4/pdb.py

Question de suivi

De plus, le Python Essential Reference de David Beazley l'explique comme suit: «L'option -m exécute un module de bibliothèque comme un script qui s'exécute à l'intérieur du __main__module avant l'exécution du script principal».

Cela signifie que tout module que vous pouvez rechercher avec une instruction d'importation peut être exécuté comme point d'entrée du programme - s'il a un bloc de code, généralement vers la fin, avec if __name__ == '__main__':.

-m sans ajouter le répertoire courant au chemin:

Un commentaire ici ailleurs dit:

Que l'option -m ajoute également le répertoire courant à sys.path, est évidemment un problème de sécurité (voir: attaque de préchargement). Ce comportement est similaire à l'ordre de recherche des bibliothèques dans Windows (avant qu'il ne soit durci récemment). C'est dommage que Python ne suive pas la tendance et n'offre pas un moyen simple de désactiver l'ajout. vers sys.path

Eh bien, cela démontre le problème possible - (dans Windows, supprimez les guillemets):

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

Utilisez l' -Iindicateur pour verrouiller cela pour les environnements de production (nouveau dans la version 3.4):

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

à partir de la documentation :

-I

Exécutez Python en mode isolé. Cela implique également -E et -s. En mode isolé, sys.path ne contient ni le répertoire du script ni le répertoire site-packages de l'utilisateur. Toutes les variables d'environnement PYTHON * sont également ignorées. D'autres restrictions peuvent être imposées pour empêcher l'utilisateur d'injecter du code malveillant.

Que fait __package__-on?

Cela permet des importations relatives explicites, pas particulièrement pertinentes pour cette question, cependant - voir cette réponse ici: Quel est le but de l'attribut «__package__» en Python?

Salle Aaron
la source
Quel chemin est ajouté au sys.path lorsque le commutateur -m est utilisé?
variable du
Je l'ai déjà cité, "Comme avec l'option -c, le répertoire courant sera ajouté au début de sys.path." mais j'ai clarifié ce à quoi la citation fait référence.
Aaron Hall
Je veux dire que - supposons que dans le répertoire D: \ test, j'exécute la commande - python -m foo.bar.boo alors cela ajoutera-t-il le dossier d'installation de python ou le répertoire D: \ test à sys.path? Je crois comprendre que cela ajoutera d: \ test à sys.path, importera foo.bar et exécutera le script boo
variable
@variable - oui, essayez-le.
Aaron Hall
1

La principale raison d'exécuter un module (ou package) en tant que script avec -m est de simplifier le déploiement, en particulier sous Windows. Vous pouvez installer des scripts au même endroit dans la bibliothèque Python où les modules vont normalement - au lieu de polluer PATH ou des répertoires exécutables globaux tels que ~ / .local (le répertoire de scripts par utilisateur est ridiculement difficile à trouver dans Windows).

Ensuite, vous tapez simplement -m et Python trouve le script automatiquement. Par exemple, python -m piptrouvera le bon pip pour la même instance de l'interpréteur Python qui l'exécute. Sans -m, si l'utilisateur a plusieurs versions de Python installées, laquelle serait le pip "global"?

Si l'utilisateur préfère les points d'entrée "classiques" pour les scripts de ligne de commande, ceux-ci peuvent être facilement ajoutés sous forme de petits scripts quelque part dans PATH, ou pip peut les créer au moment de l'installation avec le paramètre entry_points dans setup.py.

Il suffit donc de vérifier __name__ == '__main__'et d'ignorer les autres détails d'implémentation non fiables.

ddbug
la source
Que l'option -m ajoute également le répertoire courant à sys.path, est évidemment un problème de sécurité (voir: attaque de préchargement ). Ce comportement est similaire à l'ordre de recherche des bibliothèques dans Windows (avant qu'il ne soit durci récemment). C'est dommage que Python ne suive pas la tendance et n'offre pas un moyen simple de désactiver l'ajout. à sys.path.
ddbug