Eksekusi kode Python dengan opsi -m atau tidak

111

Penerjemah python memiliki opsi -m modul yang "Menjalankan modul modul perpustakaan sebagai skrip".

Dengan kode python ini a.py:

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

Saya diuji python -m auntuk mendapatkan

"" <-- Empty String
__main__

sedangkan python a.pykembali

None <-- None
__main__

Bagi saya, kedua pemanggilan tersebut tampaknya sama kecuali __package__ bukan None saat dipanggil dengan opsi -m.

Menariknya, dengan python -m runpy a, saya mendapatkan yang sama python -m adengan modul python yang dikompilasi untuk mendapatkan a.pyc.

Apa perbedaan (praktis) antara doa-doa ini? Ada pro dan kontra di antara mereka?

Juga, Python Essential Reference David Beazley menjelaskannya sebagai " Opsi -m menjalankan modul perpustakaan sebagai skrip yang dijalankan di dalam modul __main__ sebelum eksekusi skrip utama ". Apa artinya?

prosseek
sumber

Jawaban:

169

Saat Anda menggunakan tanda -mbaris perintah , Python akan mengimpor modul atau paket untuk Anda, lalu menjalankannya sebagai skrip. Jika Anda tidak menggunakan -mbendera, file yang Anda beri nama dijalankan hanya sebagai skrip .

Perbedaan ini penting saat Anda mencoba menjalankan sebuah paket. Ada perbedaan besar antara:

python foo/bar/baz.py

dan

python -m foo.bar.baz

seperti dalam kasus terakhir, foo.bardiimpor dan impor relatif akan berfungsi dengan benar foo.barsebagai titik awal.

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__

Akibatnya, Python harus benar-benar peduli dengan paket saat menggunakan -msakelar. Skrip normal tidak pernah bisa menjadi sebuah paket, jadi __package__diatur ke None.

Tetapi jalankan sebuah paket atau modul di dalam sebuah paket dengan -mdan sekarang setidaknya ada kemungkinan sebuah paket, jadi __package__variabel tersebut disetel ke nilai string; dalam demonstrasi di atas, ini disetel ke foo.bar, untuk modul biasa yang tidak berada di dalam paket, ini disetel ke string kosong.

Adapun __main__ modulnya ; Skrip impor Python dijalankan seperti modul biasa. Objek modul baru dibuat untuk menampung namespace global, disimpan di sys.modules['__main__']. Ini adalah apa yang __name__dirujuk variabel, itu adalah kunci dalam struktur itu.

Untuk paket, Anda dapat membuat __main__.pymodul dan menjalankannya saat dijalankan python -m package_name; sebenarnya itulah satu-satunya cara Anda dapat menjalankan paket sebagai skrip:

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

Jadi, saat menamai paket untuk dijalankan dengan -m, Python mencari __main__modul yang terdapat dalam paket itu dan menjalankannya sebagai skrip. Namanya kemudian masih disetel ke __main__, dan objek modul masih disimpan di sys.modules['__main__'].

Martijn Pieters
sumber
1
Apa sebenarnya arti perintah PYTHONPATH=test python -m foo.bar? Bisakah Anda menjelaskannya secara detail?
Andriy
3
@Andriy: PYTHONPATHmenetapkan variabel lingkungan; itu memperluas rangkaian direktori di mana Python akan mencari modul saat mengimpor; di sini ia menambahkan testdirektori ke seri tersebut. Dengan meletakkannya di baris perintah yang sama, ini hanya berlaku untuk satu pythonperintah itu. -mmemberitahu Python untuk mengimpor modul tertentu, seolah-olah Anda berlari import foo.bar. Namun, Python akan secara otomatis menjalankan __main__modul di dalam paket sebagai skrip saat Anda menggunakan sakelar itu.
Martijn Pieters
1
having to use -m always is not that user-.friendly.Saya pikir campuran menggunakan dan tidak menggunakan -mlebih ramah pengguna.
Simin Jie
1
@SiminJie: skrip dapat dibuka di sembarang jalur sembarang dan kemudian direktori induknya ditambahkan ke jalur pencarian modul. -mhanya berfungsi untuk direktori saat ini atau direktori yang sudah terdaftar di jalur pencarian. Itu maksud saya. -mbukanlah sesuatu yang Anda berikan kepada pengguna akhir untuk masalah kegunaan itu.
Martijn Pieters
1
@ flow2k: Maksud saya itu from Photos import ...akan mengeluh. Begitu juga import Photos.<something>. import Photoshanya berfungsi karena Python mendukung paket dengan namespace (di mana dua distribusi terpisah menyediakan Photos.foodan Photos.barsecara terpisah dan mereka dapat dikelola secara independen).
Martijn Pieters
25

Eksekusi kode Python dengan opsi -m atau tidak

Gunakan -mbenderanya.

Hasilnya hampir sama ketika Anda memiliki skrip, tetapi ketika Anda mengembangkan sebuah paket, tanpa -mflag, tidak ada cara untuk membuat impor bekerja dengan benar jika Anda ingin menjalankan sub-paket atau modul dalam paket sebagai entri utama arahkan ke program Anda (dan percayalah, saya sudah mencoba.)

Dokumen

Seperti dokumen di bendera -m yang mengatakan:

Cari sys.path untuk modul bernama dan jalankan isinya sebagai __main__modul.

dan

Seperti opsi -c, direktori saat ini akan ditambahkan ke awal sys.path.

begitu

python -m pdb

kira-kira setara dengan

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

(dengan asumsi Anda tidak memiliki paket atau skrip di direktori Anda saat ini yang disebut pdb.py)

Penjelasan:

Perilaku dibuat "dengan sengaja mirip" dengan skrip.

Banyak modul pustaka standar berisi kode yang dipanggil saat dieksekusi sebagai skrip. Contohnya adalah modul timeit:

Beberapa kode python dimaksudkan untuk dijalankan sebagai modul: (Saya pikir contoh ini lebih baik daripada contoh doc opsi baris perintah)

$ 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

Dan dari sorotan catatan rilis untuk Python 2.4 :

Opsi baris perintah -m - python -m modulename akan menemukan modul di pustaka standar, dan memanggilnya. Misalnya, python -m pdb setara denganpython /usr/lib/python2.4/pdb.py

Pertanyaan lanjutan

Juga, Referensi Esensial Python David Beazley menjelaskannya sebagai "Opsi -m menjalankan modul perpustakaan sebagai skrip yang dijalankan di dalam __main__modul sebelum eksekusi skrip utama".

Artinya, setiap modul yang dapat Anda cari dengan pernyataan impor dapat dijalankan sebagai titik masuk program - jika memiliki blok kode, biasanya di dekat akhir, dengan if __name__ == '__main__':.

-m tanpa menambahkan direktori saat ini ke jalur:

Sebuah komentar di sini di tempat lain mengatakan:

Bahwa opsi -m juga menambahkan direktori saat ini ke sys.path, jelas merupakan masalah keamanan (lihat: serangan preload). Perilaku ini mirip dengan urutan pencarian perpustakaan di Windows (sebelum dikeraskan baru-baru ini). Sayangnya Python tidak mengikuti tren dan tidak menawarkan cara sederhana untuk menonaktifkan penambahan. ke sys.path

Nah, ini menunjukkan kemungkinan masalah - (di windows hapus tanda kutip):

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

Gunakan -Itanda tersebut untuk menguncinya untuk lingkungan produksi (baru di versi 3.4):

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

dari dokumen :

-I

Jalankan Python dalam mode terisolasi. Ini juga menyiratkan -E dan -s. Dalam mode terisolasi, sys.path tidak berisi direktori skrip maupun direktori paket situs pengguna. Semua variabel lingkungan PYTHON * juga diabaikan. Pembatasan lebih lanjut dapat diberlakukan untuk mencegah pengguna memasukkan kode berbahaya.

Apa yang __package__dilakukannya?

Ini memungkinkan impor relatif eksplisit, tidak terlalu terkait dengan pertanyaan ini, meskipun - lihat jawaban ini di sini: Apa tujuan dari atribut "__package__" di Python?

Aaron Hall
sumber
Jalur mana yang ditambahkan ke sys.path saat sakelar -m digunakan?
variabel
Saya sudah memiliki kutipan itu, "Seperti opsi -c, direktori saat ini akan ditambahkan ke awal sys.path." tapi saya telah mengklarifikasi apa yang dimaksud kutipan itu.
Aaron Hall
Maksud saya - misalkan di direktori D: \ test, saya menjalankan perintah - python -m foo.bar.boo maka apakah ini akan menambahkan folder instalasi python atau direktori D: \ test ke sys.path? Pemahaman saya adalah bahwa itu akan menambahkan d: \ test ke sys.path, impor foo.bar dan menjalankan skrip boo
variabel
@variable - ya, cobalah.
Aaron Hall
1

Alasan utama untuk menjalankan modul (atau paket) sebagai skrip dengan -m adalah untuk menyederhanakan penerapan, terutama di Windows. Anda dapat menginstal skrip di tempat yang sama di pustaka Python tempat modul biasanya ditempatkan - alih-alih mencemari PATH atau direktori global yang dapat dieksekusi seperti ~ / .local (direktori skrip per pengguna sangat sulit ditemukan di Windows).

Kemudian Anda cukup mengetik -m dan Python menemukan skripnya secara otomatis. Misalnya, python -m pipakan menemukan pip yang benar untuk instance interpreter Python yang sama yang mengeksekusinya. Tanpa -m, jika pengguna menginstal beberapa versi Python, manakah yang akan menjadi pip "global"?

Jika pengguna lebih memilih titik masuk "klasik" untuk skrip baris perintah, ini dapat dengan mudah ditambahkan sebagai skrip kecil di suatu tempat di PATH, atau pip dapat membuatnya pada waktu penginstalan dengan parameter entry_points di setup.py.

Jadi periksa saja __name__ == '__main__'dan abaikan detail implementasi yang tidak dapat diandalkan lainnya.

ddbug
sumber
Bahwa opsi -m juga menambahkan direktori saat ini ke sys.path, jelas merupakan masalah keamanan (lihat: serangan preload ). Perilaku ini mirip dengan urutan pencarian perpustakaan di Windows (sebelum dikeraskan baru-baru ini). Sayangnya Python tidak mengikuti tren dan tidak menawarkan cara sederhana untuk menonaktifkan penambahan. ke sys.path.
ddbug