การดำเนินการรหัส Python ด้วยตัวเลือก -m หรือไม่


111

ล่ามหลามมี-m โมดูลตัวเลือกที่ "รันโมดูลห้องสมุดโมดูลเป็นสคริปต์"

ด้วยรหัส python a.py:

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

ฉันทดสอบpython -m aเพื่อรับ

"" <-- Empty String
__main__

ในขณะที่python a.pyผลตอบแทน

None <-- None
__main__

สำหรับฉันการเรียกทั้งสองนี้ดูเหมือนจะเหมือนกันยกเว้น __package__ ไม่ใช่ไม่มีเมื่อเรียกด้วยตัวเลือก -m

ที่น่าสนใจคือpython -m runpy aฉันได้รับเช่นเดียวpython -m aกับโมดูล python ที่คอมไพล์เพื่อรับ a.pyc

อะไรคือความแตกต่าง (ในทางปฏิบัติ) ระหว่างการเรียกร้องเหล่านี้? ข้อดีข้อเสียระหว่างกันหรือไม่?

นอกจากนี้ Python Essential Reference ของ David Beazley ยังอธิบายว่า " อ็อพชัน -m รันโมดูลไลบรารีเป็นสคริปต์ที่ดำเนินการภายในโมดูล __main__ ก่อนที่จะเรียกใช้สคริปต์หลัก " หมายความว่าอย่างไร?

คำตอบ:


171

เมื่อคุณใช้-mแฟล็กบรรทัดคำสั่ง Python จะนำเข้าโมดูลหรือแพ็กเกจให้คุณจากนั้นเรียกใช้เป็นสคริปต์ เมื่อคุณไม่ได้ใช้-mธงไฟล์ที่คุณชื่อมีการเรียกใช้เป็นเพียงสคริปต์

ความแตกต่างมีความสำคัญเมื่อคุณพยายามเรียกใช้แพ็คเกจ มีความแตกต่างอย่างมากระหว่าง:

python foo/bar/baz.py

และ

python -m foo.bar.baz

ในกรณีหลังfoo.barจะถูกนำเข้าและการนำเข้าแบบสัมพัทธ์จะทำงานได้อย่างถูกต้องโดยfoo.barเป็นจุดเริ่มต้น

การสาธิต:

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

ด้วยเหตุนี้ Python จึงต้องดูแลเกี่ยวกับแพ็คเกจเมื่อใช้-mสวิตช์ สคริปต์ปกติไม่สามารถจะเป็นแพคเกจเพื่อให้มีการตั้งค่า__package__None

แต่เรียกใช้แพ็กเกจหรือโมดูลภายในแพ็กเกจด้วย-mและตอนนี้อย่างน้อยก็มีความเป็นไปได้ของแพ็กเกจดังนั้น__package__ตัวแปรจึงถูกตั้งค่าเป็นค่าสตริง ในการสาธิตข้างต้นตั้งค่าเป็นfoo.barสำหรับโมดูลธรรมดาที่ไม่ได้อยู่ในแพ็คเกจจะถูกตั้งค่าเป็นสตริงว่าง

สำหรับ__main__ โมดูล ; สคริปต์นำเข้า Python กำลังทำงานเหมือนโมดูลปกติ ออบเจ็กต์โมดูลใหม่ถูกสร้างขึ้นเพื่อเก็บเนมสเปซส่วนกลางที่เก็บไว้ในsys.modules['__main__']. นี่คือสิ่งที่__name__ตัวแปรอ้างถึงซึ่งเป็นกุญแจสำคัญในโครงสร้างนั้น

สำหรับแพคเกจที่คุณสามารถสร้าง__main__.pyโมดูลและมีการทำงานที่เมื่อทำงานpython -m package_name; นั่นเป็นวิธีเดียวที่คุณสามารถเรียกใช้แพ็คเกจเป็นสคริปต์:

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

ดังนั้นเมื่อตั้งชื่อแพ็กเกจสำหรับรันด้วย-mPython จะมองหา__main__โมดูลที่มีอยู่ในแพ็กเกจนั้นและรันเป็นสคริปต์ จากนั้นชื่อจะยังคงตั้งเป็น__main__และวัตถุโมดูลยังคงถูกเก็บไว้ในsys.modules['__main__']ไฟล์.


1
คำสั่งPYTHONPATH=test python -m foo.barหมายถึงอะไร? กรุณาอธิบายรายละเอียดได้ไหม
Andriy

3
@ Andriy: PYTHONPATHตั้งค่าตัวแปรสภาพแวดล้อม มันขยายชุดของไดเร็กทอรีที่ Python จะค้นหาโมดูลเมื่อนำเข้า ที่นี่จะเพิ่มtestไดเร็กทอรีให้กับซีรี่ส์นั้น โดยใส่ไว้ในบรรทัดคำสั่งเดียวกันก็ใช้เพียงที่เดียวpythonคำสั่ง บอกงูใหญ่จะนำเข้าโมดูลที่เฉพาะเจาะจงเช่นถ้าคุณขับรถ-m import foo.barอย่างไรก็ตาม Python จะเรียกใช้__main__โมดูลภายในแพ็กเกจโดยอัตโนมัติเป็นสคริปต์เมื่อคุณใช้สวิตช์นั้น
Martijn Pieters

1
having to use -m always is not that user-.friendly.ฉันคิดว่าการผสมโดยใช้และไม่ใช้-mเป็นมิตรกับผู้ใช้น้อยกว่า
Simin Jie

1
@SiminJie: สคริปต์สามารถเปิดได้ในพา ธ ใดก็ได้จากนั้นไดเร็กทอรีพาเรนต์จะถูกเพิ่มลงในพา ธ การค้นหาโมดูล -mใช้ได้เฉพาะกับไดเร็กทอรีปัจจุบันหรือไดเร็กทอรีที่ลงทะเบียนไว้แล้วบนเส้นทางการค้นหา นั่นคือประเด็นของฉัน -mไม่ใช่สิ่งที่คุณให้กับผู้ใช้ปลายทางสำหรับปัญหาด้านความสามารถในการใช้งานนั้น
Martijn Pieters

1
@ flow2k: ฉันหมายความว่าfrom Photos import ...จะบ่น ก็import Photos.<something>เช่นกัน import Photosใช้งานได้เฉพาะเนื่องจาก Python รองรับแพ็กเกจเนมสเปซ (โดยที่สองการแจกแจงแยกกันให้Photos.fooและPhotos.barแยกกันและสามารถจัดการได้อย่างอิสระ)
Martijn Pieters

25

การดำเนินการรหัส Python ด้วยตัวเลือก -m หรือไม่

ใช้-mธง

ผลลัพธ์จะเหมือนกันมากเมื่อคุณมีสคริปต์ แต่เมื่อคุณพัฒนาแพ็คเกจโดยไม่มี-mแฟล็กจะไม่มีวิธีใดที่จะทำให้การนำเข้าทำงานได้อย่างถูกต้องหากคุณต้องการเรียกใช้แพ็กเกจย่อยหรือโมดูลในแพ็กเกจเป็นรายการหลัก ชี้ไปที่โปรแกรมของคุณ (และเชื่อฉันฉันได้ลองแล้ว)

เอกสาร

เช่นเดียวกับเอกสารบนแฟล็ก -mพูดว่า:

ค้นหา sys.path สำหรับโมดูลที่ระบุชื่อและเรียกใช้งานเนื้อหาเป็น__main__โมดูล

และ

เช่นเดียวกับอ็อพชัน -c ไดเร็กทอรีปัจจุบันจะถูกเพิ่มไปที่จุดเริ่มต้นของ sys.path

ดังนั้น

python -m pdb

เทียบเท่ากับ

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

(สมมติว่าคุณไม่มีแพ็คเกจหรือสคริปต์ในไดเร็กทอรีปัจจุบันของคุณที่เรียกว่า pdb.py)

คำอธิบาย:

พฤติกรรมถูกสร้างขึ้น "โดยเจตนาคล้ายกับ" สคริปต์

โมดูลไลบรารีมาตรฐานจำนวนมากมีโค้ดที่เรียกใช้ในการดำเนินการเป็นสคริปต์ ตัวอย่างคือโมดูล timeit:

โค้ด python บางตัวมีจุดประสงค์ให้รันเป็นโมดูล: (ฉันคิดว่าตัวอย่างนี้ดีกว่าตัวอย่าง doc option commandline)

$ 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

และจากไฮไลต์บันทึกประจำรุ่นสำหรับ Python 2.4 :

อ็อพชันบรรทัดคำสั่ง -m - python -m modulename จะค้นหาโมดูลในไลบรารีมาตรฐานและเรียกใช้ ตัวอย่างเช่นpython -m pdb เทียบเท่ากับpython /usr/lib/python2.4/pdb.py

คำถามติดตามผล

นอกจากนี้ Python Essential Reference ของ David Beazley ยังอธิบายว่า "ตัวเลือก -m รันโมดูลไลบรารีเป็นสคริปต์ที่ดำเนินการภายใน__main__โมดูลก่อนที่จะเรียกใช้สคริปต์หลัก"

มันหมายความว่าโมดูลใด ๆ ที่คุณสามารถค้นหาด้วยคำสั่งนำเข้าสามารถใช้เป็นจุดเริ่มต้นของโปรแกรม - if __name__ == '__main__':ถ้ามันมีการป้องกันรหัสมักจะใกล้ถึงจุดสิ้นสุดด้วย

-m โดยไม่ต้องเพิ่มไดเร็กทอรีปัจจุบันลงในพา ธ :

ความคิดเห็นที่นี่ที่อื่นกล่าวว่า:

ตัวเลือก -m ยังเพิ่มไดเร็กทอรีปัจจุบันไปยัง sys.path ซึ่งเห็นได้ชัดว่าเป็นปัญหาด้านความปลอดภัย (ดู: การโจมตีแบบโหลดล่วงหน้า) ลักษณะการทำงานนี้คล้ายกับลำดับการค้นหาไลบรารีใน Windows (ก่อนที่จะมีการแข็งตัวเมื่อเร็ว ๆ นี้) เป็นที่น่าเสียดายที่ Python ไม่เป็นไปตามเทรนด์และไม่มีวิธีง่ายๆในการปิดการเพิ่ม ไปที่ sys.path

นี่แสดงให้เห็นถึงปัญหาที่เป็นไปได้ - (ใน windows ลบเครื่องหมายคำพูด):

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

ใช้-Iแฟล็กเพื่อล็อกสิ่งนี้สำหรับสภาพแวดล้อมการใช้งานจริง (ใหม่ในเวอร์ชัน 3.4):

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

จากเอกสาร :

-I

เรียกใช้ Python ในโหมดแยก นี่ยังหมายถึง -E และ -s ในโหมดแยก sys.path ไม่มีทั้งไดเร็กทอรีของสคริปต์หรือไดเร็กทอรีไซต์แพ็กเกจของผู้ใช้ ตัวแปรสภาพแวดล้อม PYTHON * ทั้งหมดจะถูกละเว้นด้วย อาจมีการกำหนดข้อ จำกัด เพิ่มเติมเพื่อป้องกันไม่ให้ผู้ใช้ฉีดโค้ดที่เป็นอันตราย

อะไร__package__ทำอย่างไร

มันเปิดใช้งานการนำเข้าแบบสัมพัทธ์อย่างชัดเจนโดยเฉพาะอย่างยิ่งสำหรับคำถามนี้ - ดูคำตอบนี้ที่นี่: แอตทริบิวต์ "__package__" ใน Python มีจุดประสงค์อะไร


เส้นทางใดที่ถูกเพิ่มเข้าไปใน sys.path เมื่อใช้สวิตช์ -m
ตัวแปร

ฉันได้ยกมาแล้ว "เช่นเดียวกับตัวเลือก -c ไดเร็กทอรีปัจจุบันจะถูกเพิ่มไปที่จุดเริ่มต้นของ sys.path" แต่ฉันได้ชี้แจงสิ่งที่อ้างถึง
Aaron Hall

ฉันหมายความว่า - สมมติว่าในไดเร็กทอรี D: \ test ฉันรันคำสั่ง - python -m foo.bar.boo จากนั้นจะเพิ่มโฟลเดอร์การติดตั้ง python หรือไดเร็กทอรี D: \ test ไปยัง sys.path หรือไม่ ความเข้าใจของฉันคือมันจะเพิ่ม d: \ test ไปที่ sys.path นำเข้า foo.bar และเรียกใช้สคริปต์ boo
ตัวแปร

@variable - ใช่ลองดูสิ
Aaron Hall

1

เหตุผลหลักในการรันโมดูล (หรือแพ็กเกจ) เป็นสคริปต์ที่มี -m คือเพื่อลดความซับซ้อนในการปรับใช้โดยเฉพาะบน Windows คุณสามารถติดตั้งสคริปต์ในที่เดียวกันในไลบรารี Python ซึ่งตามปกติแล้วโมดูลจะไป - แทนที่จะทำให้ PATH เป็นมลพิษหรือไดเร็กทอรีปฏิบัติการทั่วโลกเช่น ~ / .local (ไดเร็กทอรีสคริปต์ต่อผู้ใช้นั้นหายากมากใน Windows)

จากนั้นคุณเพียงพิมพ์ -m และ Python จะค้นหาสคริปต์โดยอัตโนมัติ ตัวอย่างเช่นpython -m pipจะค้นหา pip ที่ถูกต้องสำหรับอินสแตนซ์ของ Python interpreter ซึ่งดำเนินการ หากไม่มี -m หากผู้ใช้ติดตั้ง Python หลายเวอร์ชันเวอร์ชันใดจะเป็น pip "global"

หากผู้ใช้ต้องการจุดเข้าใช้งาน "คลาสสิก" สำหรับสคริปต์บรรทัดคำสั่งคุณสามารถเพิ่มสิ่งเหล่านี้เป็นสคริปต์ขนาดเล็กที่ใดก็ได้ใน PATH หรือ pip สามารถสร้างสิ่งเหล่านี้ในเวลาติดตั้งโดยใช้พารามิเตอร์ entry_points ใน setup.py

ดังนั้นเพียงตรวจสอบ__name__ == '__main__'และละเว้นรายละเอียดการใช้งานอื่น ๆ ที่ไม่น่าเชื่อถือ


ตัวเลือก -m ยังเพิ่มไดเร็กทอรีปัจจุบันไปยัง sys.path ซึ่งเห็นได้ชัดว่าเป็นปัญหาด้านความปลอดภัย (ดู: การโจมตีแบบโหลดล่วงหน้า ) ลักษณะการทำงานนี้คล้ายกับลำดับการค้นหาไลบรารีใน Windows (ก่อนที่จะมีการแข็งตัวเมื่อเร็ว ๆ นี้) เป็นที่น่าเสียดายที่ Python ไม่เป็นไปตามเทรนด์และไม่มีวิธีง่ายๆในการปิดการเพิ่ม ไปที่ sys.path
ddbug
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.