web-dev-qa-db-fra.com

Exécution de code Python avec entrée -m ou non

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

Avec ce code python a.py:

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

J'ai testé python -m a pour obtenir

"" <-- Empty String
__main__

alors que python a.py retourne

None <-- None
__main__

Pour moi, ces deux invocations semblent être les mêmes, sauf que __package__ n'est pas None lors de l'appel avec l'option -m. 

Fait intéressant, avec python -m runpy a, j'obtiens le même que python -m a 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? 

En outre, Python Essential Reference de David Beazley l'explique comme suit: "L'option -m exécute un module de bibliothèque en tant que script qui s'exécute dans le module __main__ avant l'exécution du script principal". Qu'est-ce que ça veut dire?

56
prosseek

Lorsque vous utilisez l'indicateur de ligne de commande -m , Python importera un module ou un package, puis l'exécutera sous forme de script. Lorsque vous n'utilisez pas l'indicateur -m, le fichier que vous avez nommé est exécuté en tant que uniquement un 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 le dernier cas, foo.bar est importé et les importations relatives fonctionneront correctement avec foo.bar comme 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 __== "__main__":
>     print __package__
>     print __name__
> 
> EOF
$ PYTHONPATH=test python test/foo/bar/baz.py 
None
__main__
$ PYTHONPATH=test bin/python -m foo.bar.baz 
foo.bar
__main__

En conséquence, Python doit se soucier des paquets lors de l’utilisation du commutateur -m. Un script normal ne peut jamais être un package. __package__ est donc défini sur None.

Mais exécutez un package ou un module à l'intérieur un package avec -m et maintenant il existe au moins la possibilité d'un package, de sorte que la variable __package__ 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 en clair non contenus dans un package, il est défini sur une chaîne vide.

En ce qui concerne le __main__module; Python importe les scripts en cours d'exécution comme s'il s'agissait d'un module standard. Un nouvel objet de module est créé pour contenir l'espace de noms global, stocké dans sys.modules['__main__']. C’est ce à quoi la variable __name__ fait référence, c’est une clé dans cette structure.

Pour les packages, vous pouvez créer un module __main__.py et le faire exécuter lors de l'exécution de python -m package_name; en fait, c’est la seule façon dont vous pouvez exécutez un package sous forme de 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, lorsque vous nommez un package à exécuter avec -m, Python recherche un module __main__ 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__'].

86
Martijn Pieters

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

Utilisez le drapeau -m

Les résultats sont sensiblement les mêmes lorsque vous avez un script, mais lorsque vous développez un package, sans l'indicateur -m, il est impossible de faire en sorte que les importations fonctionnent correctement si vous souhaitez exécuter un sous-package ou un module dans le package en tant que principal. point d’entrée dans votre programme (et croyez-moi, j’ai essayé.)

Les docs

Comme les docs say:

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

et

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

alors

python -m pdb

est à peu près équivalent à

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

(en supposant que vous ne disposiez pas d'un paquet ou d'un script dans votre répertoire actuel appelé pdb.py)

Explication:

Le comportement est fait "délibérément similaire à" des scripts.

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

Certains codes python sont destinés à être exécutés en tant que en tant que module: (je pense que cet exemple est meilleur que celui de l'option doc en 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 de la note de publication met en évidence pour Python 2.4 :

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

Question de suivi

En outre, Python Essential Reference de David Beazley l'explique comme suit: "L'option -M exécute un module de bibliothèque en tant que script qui s'exécute dans le module __main__ 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é en tant que point d'entrée du programme - s'il comporte un bloc de code, généralement proche de la fin, avec if __== '__main__':.

-m sans ajouter le répertoire en cours au chemin:

Un commentaire ici ailleurs dit:

Le fait que l'option -m ajoute également le répertoire en cours à sys.path est évidemment un problème de sécurité (voir: attaque de préchargement). Ce comportement est similaire à l’ordre de recherche de la bibliothèque dans Windows (avant qu’il ait été récemment renforcé). 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 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'indicateur -I pour verrouiller cette option dans les environnements de production (nouveauté de la version 3.4):

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

de les docs :

-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. Des restrictions supplémentaires peuvent être imposées pour empêcher l'utilisateur d'injecter du code malveillant.

Que fait __package__?

Il permet cependant des importations relatives explicites, qui ne sont pas particulièrement liées à cette question - voir cette réponse ici: Quel est le but de l'attribut "__package__" en Python?

13
Aaron Hall

La principale raison d’exécuter un module (ou un package) en tant que script avec -m est pour 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 des répertoires PATH ou 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 pip trouvera le bon pip pour la même instance d'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 facilement être 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 __== '__main__' et d'ignorer les autres détails d'implémentation non fiables. 

0
ddbug