Thực thi mã Python với tùy chọn -m hoặc không


111

Các thông dịch viên python có -m mô-đun tùy chọn đó "Chạy mô-đun thư viện mô-đun như một kịch bản".

Với mã python này a.py:

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

Tôi đã thử nghiệm python -m ađể có được

"" <-- Empty String
__main__

trong khi python a.pytrở lại

None <-- None
__main__

Đối với tôi, hai lệnh gọi đó dường như giống nhau ngoại trừ __package__ không phải là Không khi được gọi với tùy chọn -m.

Thật thú vị, với python -m runpy a, tôi nhận được tương tự như python -m avới mô-đun python được biên dịch để có được a.pyc.

Sự khác biệt (thực tế) giữa những lời gọi này là gì? Bất kỳ ưu và khuyết điểm giữa chúng?

Ngoài ra, Tài liệu tham khảo về Python Essential của David Beazley giải thích nó là " Tùy chọn -m chạy mô-đun thư viện dưới dạng tập lệnh thực thi bên trong mô-đun __main__ trước khi thực thi tập lệnh chính ". Nó có nghĩa là gì?

Câu trả lời:


169

Khi bạn sử dụng -mcờ dòng lệnh , Python sẽ nhập một mô-đun hoặc gói cho bạn, sau đó chạy nó dưới dạng tập lệnh. Khi bạn không sử dụng -mcờ, tệp bạn đặt tên chỉ được chạy dưới dạng một tập lệnh .

Sự phân biệt rất quan trọng khi bạn cố gắng chạy một gói. Có một sự khác biệt lớn giữa:

python foo/bar/baz.py

python -m foo.bar.baz

như trong trường hợp sau, foo.barđược nhập và nhập tương đối sẽ hoạt động chính xác với foo.barđiểm bắt đầu.

Bản giới thiệu:

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

Do đó, Python phải thực sự quan tâm đến các gói khi sử dụng -mswitch. Một tập lệnh bình thường không bao giờ có thể một gói, vì vậy __package__được đặt thành None.

Nhưng hãy chạy một gói hoặc mô-đun bên trong một gói -mvà bây giờ có ít nhất khả năng có một gói, vì vậy __package__biến được đặt thành giá trị chuỗi; trong phần minh họa ở trên, nó được đặt thành foo.bar, đối với các mô-đun thuần túy không nằm trong một gói, nó được đặt thành một chuỗi trống.

Đối với __main__ mô-đun ; Python nhập các tập lệnh đang được chạy như một mô-đun thông thường. Một đối tượng mô-đun mới được tạo để giữ không gian tên chung, được lưu trữ trong sys.modules['__main__']. Đây là những gì __name__biến đề cập đến, nó là một chìa khóa trong cấu trúc đó.

Đối với các gói, bạn có thể tạo một __main__.pymô-đun và có mô-đun đó khi chạy python -m package_name; thực tế đó là cách duy nhất bạn có thể chạy một gói dưới dạng tập lệnh:

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

Vì vậy, khi đặt tên cho một gói để chạy với -m, Python sẽ tìm kiếm một __main__mô-đun có trong gói đó và thực thi nó dưới dạng một tập lệnh. Tên của nó sau đó vẫn được đặt thành __main__, và đối tượng mô-đun vẫn được lưu trữ trong sys.modules['__main__'].


1
Lệnh thực sự PYTHONPATH=test python -m foo.barnghĩa là gì? Bạn có thể giải thích nó chi tiết, xin vui lòng?
Andriy

3
@Andriy: PYTHONPATHđặt một biến môi trường; nó mở rộng chuỗi thư mục nơi Python sẽ tìm kiếm các mô-đun khi nhập; ở đây nó thêm testthư mục vào loạt bài đó. Bằng cách đặt nó trên cùng một dòng lệnh, nó chỉ áp dụng cho pythonlệnh duy nhất đó . -myêu cầu Python nhập một mô-đun cụ thể, như thể bạn đã chạy import foo.bar. Tuy nhiên, Python sẽ tự động chạy một __main__mô-đun bên trong một gói dưới dạng tập lệnh khi bạn sử dụng công tắc đó.
Martijn Pieters

1
having to use -m always is not that user-.friendly.Tôi nghĩ rằng kết hợp sử dụng và không sử dụng -mít thân thiện với người dùng hơn.
Simin Jie

1
@SiminJie: các tập lệnh có thể được mở trong bất kỳ đường dẫn tùy ý nào và sau đó thư mục mẹ của chúng được thêm vào đường dẫn tìm kiếm mô-đun. -mchỉ hoạt động đối với thư mục hiện tại hoặc các thư mục đã được đăng ký trên đường dẫn tìm kiếm. Đó là quan điểm của tôi. -mkhông phải là thứ bạn cung cấp cho người dùng cuối vì vấn đề rất hữu ích đó.
Martijn Pieters

1
@ flow2k: Ý tôi là from Photos import ...sẽ phàn nàn. Cũng vậy import Photos.<something>. import Photoschỉ hoạt động vì Python hỗ trợ namespaced gói (nơi hai phân bố riêng biệt cung cấp Photos.fooPhotos.barriêng biệt và họ độc lập có thể được quản lý).
Martijn Pieters

25

Thực thi mã Python với tùy chọn -m hoặc không

Sử dụng -mcờ.

Kết quả khá giống nhau khi bạn có một tập lệnh, nhưng khi bạn phát triển một gói, không có -mcờ, không có cách nào để nhập hoạt động chính xác nếu bạn muốn chạy một gói con hoặc mô-đun trong gói làm mục nhập chính. chỉ vào chương trình của bạn (và tin tôi đi, tôi đã thử.)

Các tài liệu

Giống như tài liệu trên cờ -m nói:

Tìm kiếm sys.path cho mô-đun được đặt tên và thực thi nội dung của nó dưới dạng __main__mô-đun.

Như với tùy chọn -c, thư mục hiện tại sẽ được thêm vào phần đầu của sys.path.

vì thế

python -m pdb

gần tương đương với

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

(giả sử bạn không có gói hoặc tập lệnh trong thư mục hiện tại của mình có tên là pdb.py)

Giải trình:

Hành vi được thực hiện "cố ý giống với" kịch bản.

Nhiều mô-đun thư viện tiêu chuẩn chứa mã được gọi khi thực thi chúng dưới dạng tập lệnh. Một ví dụ là mô-đun thời gian:

Một số mã python được thiết kế để chạy dưới dạng mô-đun: (Tôi nghĩ ví dụ này tốt hơn ví dụ doc tùy chọn dòng lệnh)

$ 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

Và từ các điểm nổi bật của ghi chú phát hành cho Python 2.4 :

Tùy chọn dòng lệnh -m - tên mô-đun python -m sẽ tìm một mô-đun trong thư viện chuẩn và gọi nó. Ví dụ, python -m pdb tương đương vớipython /usr/lib/python2.4/pdb.py

Theo dõi câu hỏi

Ngoài ra, Tài liệu tham khảo về Python Essential của David Beazley giải thích nó là "Tùy chọn -m chạy mô-đun thư viện như một tập lệnh thực thi bên trong __main__mô-đun trước khi thực thi tập lệnh chính".

Nó có nghĩa là bất kỳ mô-đun nào bạn có thể tra cứu bằng câu lệnh nhập đều có thể được chạy như là điểm đầu vào của chương trình - nếu nó có một khối mã, thường ở gần cuối, với if __name__ == '__main__':.

-m mà không cần thêm thư mục hiện tại vào đường dẫn:

Một bình luận ở đây ở nơi khác nói:

Tùy chọn -m đó cũng thêm thư mục hiện tại vào sys.path, rõ ràng là một vấn đề bảo mật (xem: tấn công tải trước). Hành vi này tương tự như thứ tự tìm kiếm thư viện trong Windows (trước khi nó được cứng lại gần đây). Thật đáng tiếc khi Python không theo xu hướng và không cung cấp một cách đơn giản để tắt thêm. tới sys.path

Chà, điều này chứng tỏ sự cố có thể xảy ra - (trong windows, hãy xóa dấu ngoặc kép):

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

Sử dụng -Icờ để khóa điều này cho môi trường sản xuất (mới trong phiên bản 3.4):

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

từ các tài liệu :

-I

Chạy Python ở chế độ cô lập. Điều này cũng ngụ ý -E và -s. Ở chế độ biệt lập, sys.path không chứa thư mục của script cũng như thư mục site-package của người dùng. Tất cả các biến môi trường PYTHON * cũng bị bỏ qua. Các hạn chế khác có thể được áp đặt để ngăn người dùng tiêm mã độc.

Làm gì __package__?

Tuy nhiên, nó cho phép nhập tương đối rõ ràng, không đặc biệt là vi phạm đối với câu hỏi này - hãy xem câu trả lời này tại đây: Mục đích của thuộc tính "__package__" trong Python là gì?


Đường dẫn nào được thêm vào sys.path khi sử dụng công tắc -m?
biến

Tôi đã có trích dẫn đó, "Như với tùy chọn -c, thư mục hiện tại sẽ được thêm vào phần đầu của sys.path." nhưng tôi đã làm rõ những gì trích dẫn đề cập đến.
Aaron Hall

Ý tôi là - giả sử trong thư mục D: \ test, tôi chạy lệnh - python -m foo.bar.boo thì điều này sẽ thêm thư mục cài đặt python hoặc thư mục D: \ test vào sys.path? Tôi hiểu là nó sẽ thêm d: \ test vào sys.path, nhập foo.bar và chạy boo script
biến

@variable - vâng, hãy thử.
Aaron Hall

1

Lý do chính để chạy mô-đun (hoặc gói) dưới dạng tập lệnh với -m là để đơn giản hóa việc triển khai, đặc biệt là trên Windows. Bạn có thể cài đặt các tập lệnh ở cùng một nơi trong thư viện Python nơi các mô-đun thường đi - thay vì gây ô nhiễm PATH hoặc các thư mục thực thi toàn cục như ~ / .local (thư mục tập lệnh cho mỗi người dùng rất khó tìm thấy trong Windows).

Sau đó, bạn chỉ cần nhập -m và Python tự động tìm thấy tập lệnh. Ví dụ, python -m pipsẽ tìm đúng pip cho cùng một phiên bản của trình thông dịch Python thực thi nó. Nếu không có -m, nếu người dùng đã cài đặt một số phiên bản Python, thì phiên bản nào sẽ là pip "toàn cầu"?

Nếu người dùng thích các điểm nhập "cổ điển" cho các tập lệnh dòng lệnh, chúng có thể dễ dàng được thêm vào dưới dạng các tập lệnh nhỏ ở đâu đó trong PATH hoặc pip có thể tạo các điểm này tại thời điểm cài đặt với tham số entry_points trong setup.py.

Vì vậy, chỉ cần kiểm tra __name__ == '__main__'và bỏ qua các chi tiết triển khai không đáng tin cậy khác.


Tùy chọn -m đó cũng thêm thư mục hiện tại vào sys.path, rõ ràng là một vấn đề bảo mật (xem: tấn công tải trước ). Hành vi này tương tự như thứ tự tìm kiếm thư viện trong Windows (trước khi nó được cứng lại gần đây). Thật đáng tiếc khi Python không theo xu hướng và không cung cấp một cách đơn giản để tắt thêm. tới sys.path.
ddbug
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.