Ejecución de código Python con la opción -m o no

111

El intérprete de Python tiene la opción de -m módulo que "Ejecuta el módulo del módulo de la biblioteca como un script".

Con este código de Python a.py:

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

Probé python -m apara conseguir

"" <-- Empty String
__main__

mientras que python a.pydevuelve

None <-- None
__main__

Para mí, esas dos invocaciones parecen ser iguales, excepto que __package__ no es None cuando se invoca con la opción -m.

Curiosamente, con python -m runpy a, obtengo lo mismo que python -m acon el módulo de python compilado para obtener a.pyc.

¿Cuál es la diferencia (práctica) entre estas invocaciones? ¿Pros y contras entre ellos?

Además, la Referencia esencial de Python de David Beazley lo explica como " La opción -m ejecuta un módulo de biblioteca como un script que se ejecuta dentro del módulo __main__ antes de la ejecución del script principal ". Qué significa eso?

prosseek
fuente

Respuestas:

169

Cuando use la -mmarca de línea de comandos , Python importará un módulo o paquete por usted, luego lo ejecutará como un script. Cuando no usa la -mbandera, el archivo que nombró se ejecuta solo como un script .

La distinción es importante cuando intenta ejecutar un paquete. Hay una gran diferencia entre:

python foo/bar/baz.py

y

python -m foo.bar.baz

como en el último caso, foo.barse importa y las importaciones relativas funcionarán correctamente con foo.barel punto de partida.

Manifestación:

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

Como resultado, Python tiene que preocuparse realmente por los paquetes cuando usa el -mconmutador. Un script normal nunca puede ser un paquete, por lo que __package__se establece en None.

Pero ejecute un paquete o módulo dentro de un paquete con -my ahora existe al menos la posibilidad de un paquete, por lo que la __package__variable se establece en un valor de cadena; en la demostración anterior se establece en foo.bar, para módulos simples que no están dentro de un paquete, se establece en una cadena vacía.

En cuanto al __main__ módulo ; Python importa los scripts que se ejecutan como lo haría con un módulo normal. Se crea un nuevo objeto de módulo para contener el espacio de nombres global, almacenado en sys.modules['__main__']. A esto se __name__refiere la variable, es clave en esa estructura.

Para los paquetes, puede crear un __main__.pymódulo y hacer que se ejecute cuando se ejecute python -m package_name; de hecho, esa es la única forma en que puede ejecutar un paquete como 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__

Entonces, al nombrar un paquete para ejecutarlo -m, Python busca un __main__módulo contenido en ese paquete y lo ejecuta como un script. Su nombre aún se establece en __main__y el objeto del módulo aún se almacena en sys.modules['__main__'].

Martijn Pieters
fuente
1
¿Qué significa realmente el comando PYTHONPATH=test python -m foo.bar? ¿Podría explicarlo en detalle, por favor?
Andriy
3
@Andriy: PYTHONPATHestablece una variable de entorno; expande la serie de directorios donde Python buscará módulos al importar; aquí agrega el testdirectorio a esa serie. Al ponerlo en la misma línea de comando, se aplica solo a ese pythoncomando. -mle dice a Python que importe un módulo específico, como si ejecutara import foo.bar. Sin embargo, Python ejecutará automáticamente un __main__módulo dentro de un paquete como un script cuando use ese interruptor.
Martijn Pieters
1
having to use -m always is not that user-.friendly.Creo que mezclar el uso y el no uso -mes menos fácil de usar.
Simin Jie
1
@SiminJie: los scripts se pueden abrir en cualquier ruta arbitraria y luego su directorio principal se agrega a la ruta de búsqueda del módulo. -msolo funciona para el directorio actual o directorios ya registrados en la ruta de búsqueda. Ese era mi punto. -mno es algo que le dé a los usuarios finales por ese mismo problema de usabilidad.
Martijn Pieters
1
@ flow2k: quiero decir que from Photos import ...se quejará. También lo haría import Photos.<something>. import Photossólo funciona gracias a los apoyos de espacios de nombres de Python paquetes (donde dos distribuciones separadas proporcionan Photos.fooy Photos.barpor separado y de forma independiente que se pueden gestionar).
Martijn Pieters
25

Ejecución de código Python con la opción -m o no

Usa la -mbandera.

Los resultados son prácticamente los mismos cuando tiene un script, pero cuando desarrolla un paquete, sin el -mindicador, no hay forma de que las importaciones funcionen correctamente si desea ejecutar un subpaquete o módulo en el paquete como entrada principal apunte a su programa (y créame, lo he intentado).

Los docs

Como los documentos en la bandera -m, di:

Busque sys.path para el módulo nombrado y ejecute su contenido como el __main__módulo.

y

Al igual que con la opción -c, el directorio actual se agregará al inicio de sys.path.

entonces

python -m pdb

es aproximadamente equivalente a

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

(asumiendo que no tiene un paquete o script en su directorio actual llamado pdb.py)

Explicación:

El comportamiento se hace "deliberadamente similar" a los guiones.

Muchos módulos de biblioteca estándar contienen código que se invoca en su ejecución como un script. Un ejemplo es el módulo timeit:

Parte del código de Python está destinado a ejecutarse como un módulo: (creo que este ejemplo es mejor que el ejemplo de documento de la opción de línea de comandos)

$ 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

Y de los aspectos más destacados de la nota de lanzamiento de Python 2.4 :

La opción de línea de comando -m - python -m modulename encontrará un módulo en la biblioteca estándar y lo invocará. Por ejemplo, python -m pdb es equivalente apython /usr/lib/python2.4/pdb.py

Siguiente pregunta

Además, la Referencia esencial de Python de David Beazley lo explica como "La opción -m ejecuta un módulo de biblioteca como un script que se ejecuta dentro del __main__módulo antes de la ejecución del script principal".

Significa que cualquier módulo que pueda buscar con una declaración de importación se puede ejecutar como el punto de entrada del programa, si tiene un bloque de código, generalmente cerca del final, con if __name__ == '__main__':.

-m sin agregar el directorio actual a la ruta:

Un comentario aquí en otro lugar dice:

Que la opción -m también agregue el directorio actual a sys.path, es obviamente un problema de seguridad (ver: ataque de precarga). Este comportamiento es similar al orden de búsqueda de la biblioteca en Windows (antes de que se hubiera reforzado recientemente). Es una pena que Python no siga la tendencia y no ofrezca una forma sencilla de deshabilitar la adición. a sys.path

Bueno, esto demuestra el posible problema (en Windows, elimine las comillas):

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

Use la -Ibandera para bloquear esto para entornos de producción (nuevo en la versión 3.4):

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

de los documentos :

-I

Ejecute Python en modo aislado. Esto también implica -E y -s. En modo aislado, sys.path no contiene ni el directorio del script ni el directorio de paquetes del sitio del usuario. Todas las variables de entorno PYTHON * también se ignoran. Se pueden imponer restricciones adicionales para evitar que el usuario inyecte código malicioso.

¿Qué __package__hacer?

Sin embargo, habilita importaciones relativas explícitas, no particularmente relacionadas con esta pregunta; vea esta respuesta aquí: ¿Cuál es el propósito del atributo "__package__" en Python?

Aaron Hall
fuente
¿Qué ruta se agrega a sys.path cuando se usa el modificador -m?
variable
Ya lo he citado, "Al igual que con la opción -c, el directorio actual se agregará al inicio de sys.path". pero he aclarado a qué se refiere la cita.
Aaron Hall
Quiero decir que, supongamos que en el directorio D: \ test, ejecuto el comando - python -m foo.bar.boo, ¿agregará esto la carpeta de instalación de Python o el directorio D: \ test a sys.path? Tengo entendido que agregará d: \ test a sys.path, importará foo.bar y ejecutará el script boo
variable
@variable - sí, pruébalo.
Aaron Hall
1

La razón principal para ejecutar un módulo (o paquete) como un script con -m es simplificar la implementación, especialmente en Windows. Puede instalar scripts en el mismo lugar de la biblioteca de Python donde normalmente van los módulos, en lugar de contaminar PATH o directorios ejecutables globales como ~ / .local (el directorio de scripts por usuario es ridículamente difícil de encontrar en Windows).

Luego, simplemente escribe -my Python encuentra el script automáticamente. Por ejemplo, python -m pipencontrará el pip correcto para la misma instancia del intérprete de Python que lo ejecuta. Sin -m, si el usuario tiene varias versiones de Python instaladas, ¿cuál sería el pip "global"?

Si el usuario prefiere puntos de entrada "clásicos" para los scripts de línea de comandos, estos se pueden agregar fácilmente como pequeños scripts en algún lugar de PATH, o pip puede crearlos en el momento de la instalación con el parámetro entry_points en setup.py.

Por lo tanto, solo verifique __name__ == '__main__'e ignore otros detalles de implementación no confiables.

ddbug
fuente
Que la opción -m también agregue el directorio actual a sys.path, es obviamente un problema de seguridad (ver: ataque de precarga ). Este comportamiento es similar al orden de búsqueda de la biblioteca en Windows (antes de que se hubiera reforzado recientemente). Es una pena que Python no siga la tendencia y no ofrezca una forma sencilla de deshabilitar la adición. a sys.path.
ddbug