Esecuzione di codice Python con l'opzione -m o meno


111

L'interprete Python ha l' opzione -m modulo che "Esegue il modulo del modulo della libreria come uno script".

Con questo codice Python a.py:

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

Ho provato python -m aper ottenere

"" <-- Empty String
__main__

mentre python a.pyritorna

None <-- None
__main__

Per me, queste due invocazioni sembrano essere le stesse tranne che __package__ non è None quando invocato con l'opzione -m.

È interessante notare che, con python -m runpy a, ottengo lo stesso del python -m amodulo python compilato per ottenere a.pyc.

Qual è la differenza (pratica) tra queste invocazioni? Ci sono pro e contro tra di loro?

Inoltre, Python Essential Reference di David Beazley lo spiega come " L'opzione -m esegue un modulo di libreria come uno script che viene eseguito all'interno del modulo __main__ prima dell'esecuzione dello script principale ". Cosa significa?

Risposte:


169

Quando usi il -mflag della riga di comando , Python importerà un modulo o un pacchetto per te, quindi lo eseguirà come uno script. Quando non usi il -mflag, il file che hai nominato viene eseguito come uno script .

La distinzione è importante quando si tenta di eseguire un pacchetto. C'è una grande differenza tra:

python foo/bar/baz.py

e

python -m foo.bar.baz

come in quest'ultimo caso, foo.barviene importato e le relative importazioni funzioneranno correttamente con foo.barcome punto di partenza.

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__

Di conseguenza, Python deve effettivamente preoccuparsi dei pacchetti quando utilizza lo -mswitch. Uno script normale non può mai essere un pacchetto, quindi __package__è impostato su None.

Ma esegui un pacchetto o un modulo all'interno di un pacchetto con -me ora c'è almeno la possibilità di un pacchetto, quindi la __package__variabile è impostata su un valore stringa; nella dimostrazione precedente è impostato su foo.bar, per i moduli semplici non all'interno di un pacchetto, è impostato su una stringa vuota.

Per quanto riguarda il __main__ modulo ; Python importa gli script eseguiti come un normale modulo. Viene creato un nuovo oggetto modulo per contenere lo spazio dei nomi globale, archiviato in sys.modules['__main__']. Questo è ciò __name__a cui si riferisce la variabile, è una chiave in quella struttura.

Per i pacchetti, puoi creare un __main__.pymodulo e farlo funzionare durante l'esecuzione python -m package_name; nel fatto che è l'unico modo è possibile eseguire un pacchetto come uno 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__

Quindi, quando si nomina un pacchetto per l'esecuzione con -m, Python cerca un __main__modulo contenuto in quel pacchetto e lo esegue come uno script. Il suo nome è quindi ancora impostato su __main__e l'oggetto modulo è ancora memorizzato in sys.modules['__main__'].


1
Cosa significa effettivamente comando PYTHONPATH=test python -m foo.bar? Puoi spiegarlo in dettaglio, per favore?
Andriy

3
@Andriy: PYTHONPATHimposta una variabile d'ambiente; espande la serie di directory in cui Python cercherà i moduli durante l'importazione; qui aggiunge la testdirectory a quella serie. Mettendolo sulla stessa riga di comando, si applica solo a quel singolo pythoncomando. -mdice a Python di importare un modulo specifico, come se tu avessi eseguito import foo.bar. Tuttavia, Python eseguirà automaticamente un __main__modulo all'interno di un pacchetto come uno script quando si utilizza tale opzione.
Martijn Pieters

1
having to use -m always is not that user-.friendly.Penso che mescolare usando e non usando -msia meno facile da usare.
Simin Jie

1
@SiminJie: gli script possono essere aperti in qualsiasi percorso arbitrario e quindi la loro directory principale viene aggiunta al percorso di ricerca del modulo. -mfunziona solo per la directory corrente o le directory già registrate nel percorso di ricerca. Questo era il mio punto. -mnon è qualcosa che dai agli utenti finali proprio per quel problema di usabilità.
Martijn Pieters

1
@ flow2k: voglio dire che from Photos import ...si lamenterà. Così sarebbe import Photos.<something>. import Photosfunziona solo perché supporti Python namespace pacchetti (dove due distribuzioni particolari dispongano Photos.fooe Photos.barseparatamente e possono essere gestiti in modo indipendente).
Martijn Pieters

25

Esecuzione di codice Python con l'opzione -m o meno

Usa la -mbandiera.

I risultati sono praticamente gli stessi quando si ha uno script, ma quando si sviluppa un pacchetto, senza il -mflag, non c'è modo di far funzionare correttamente le importazioni se si desidera eseguire un sottopacchetto o un modulo nel pacchetto come voce principale indica il tuo programma (e credimi, ci ho provato).

I documenti

Come dicono i documenti sulla bandiera -m :

Cerca in sys.path il modulo denominato ed esegui il suo contenuto come __main__modulo.

e

Come con l'opzione -c, la directory corrente verrà aggiunta all'inizio di sys.path.

così

python -m pdb

è più o meno equivalente a

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

(supponendo che tu non abbia un pacchetto o uno script nella tua directory corrente chiamato pdb.py)

Spiegazione:

Il comportamento è reso "deliberatamente simile agli script".

Molti moduli della libreria standard contengono codice che viene richiamato durante la loro esecuzione come script. Un esempio è il modulo timeit:

Un po 'di codice Python è pensato per essere eseguito come un modulo: (Penso che questo esempio sia migliore dell'esempio doc dell'opzione della riga di comando)

$ 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

E dalle note di rilascio in evidenza per Python 2.4 :

L'opzione della riga di comando -m - python -m modulename troverà un modulo nella libreria standard e lo invocerà. Ad esempio, python -m pdb è equivalente apython /usr/lib/python2.4/pdb.py

Domanda di follow-up

Inoltre, Python Essential Reference di David Beazley lo spiega come "L'opzione -m esegue un modulo di libreria come uno script che viene eseguito all'interno del __main__modulo prima dell'esecuzione dello script principale".

Significa che qualsiasi modulo che puoi cercare con un'istruzione import può essere eseguito come punto di ingresso del programma - se ha un blocco di codice, di solito vicino alla fine, con if __name__ == '__main__':.

-m senza aggiungere la directory corrente al percorso:

Un commento qui altrove dice:

Che l'opzione -m aggiunga anche la directory corrente a sys.path, è ovviamente un problema di sicurezza (vedi: attacco precaricato). Questo comportamento è simile all'ordine di ricerca della libreria in Windows (prima che fosse rafforzato di recente). È un peccato che Python non segua la tendenza e non offra un modo semplice per disabilitare l'aggiunta. a sys.path

Bene, questo dimostra il possibile problema - (in Windows rimuovere le virgolette):

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

Usa il -Iflag per bloccarlo per gli ambienti di produzione (nuovo nella versione 3.4):

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

dai documenti :

-I

Esegui Python in modalità isolata. Ciò implica anche -E e -s. In modalità isolata, sys.path non contiene né la directory dello script né la directory dei pacchetti del sito dell'utente. Anche tutte le variabili d'ambiente PYTHON * vengono ignorate. Possono essere imposte ulteriori restrizioni per impedire all'utente di iniettare codice dannoso.

Cosa fa __package__?

Abilita le importazioni relative esplicite, non particolarmente pertinenti a questa domanda, tuttavia - vedi questa risposta qui: Qual è lo scopo dell'attributo "__package__" in Python?


Quale percorso viene aggiunto a sys.path quando viene utilizzata l'opzione -m?
variabile

Ho già citato, "Come con l'opzione -c, la directory corrente verrà aggiunta all'inizio di sys.path." ma ho chiarito a cosa si riferisce la citazione.
Aaron Hall

Voglio dire che - supponiamo che nella directory D: \ test, esegua il comando - python -m foo.bar.boo, quindi questo aggiungerà la cartella di installazione di python o la directory D: \ test a sys.path? La mia comprensione è che aggiungerà d: \ test a sys.path, importerà foo.bar ed eseguirà lo script boo
variabile

@variable - sì, provalo.
Aaron Hall

1

Il motivo principale per eseguire un modulo (o pacchetto) come script con -m è semplificare la distribuzione, specialmente su Windows. È possibile installare gli script nella stessa posizione nella libreria Python in cui normalmente vanno i moduli, invece di inquinare PATH o directory eseguibili globali come ~ / .local (la directory degli script per utente è incredibilmente difficile da trovare in Windows).

Quindi digita -m e Python trova lo script automaticamente. Ad esempio, python -m piptroverà il pip corretto per la stessa istanza dell'interprete Python che lo esegue. Senza -m, se l'utente ha diverse versioni di Python installate, quale sarebbe il pip "globale"?

Se l'utente preferisce i punti di ingresso "classici" per gli script della riga di comando, questi possono essere facilmente aggiunti come piccoli script da qualche parte in PATH, oppure pip può crearli al momento dell'installazione con il parametro entry_points in setup.py.

Quindi controlla __name__ == '__main__'e ignora altri dettagli di implementazione non affidabili.


Che l'opzione -m aggiunga anche la directory corrente a sys.path, è ovviamente un problema di sicurezza (vedi: attacco precaricato ). Questo comportamento è simile all'ordine di ricerca della libreria in Windows (prima che fosse rafforzato di recente). È un peccato che Python non segua la tendenza e non offra un modo semplice per disabilitare l'aggiunta. a sys.path.
ddbug
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.