ฉันจะสร้างแพ็คเกจเนมสเปซใน Python ได้อย่างไร


141

ใน Python แพ็คเกจเนมสเปซช่วยให้คุณสามารถแพร่กระจายรหัส Python ในหลายโครงการ สิ่งนี้มีประโยชน์เมื่อคุณต้องการปล่อยไลบรารีที่เกี่ยวข้องเป็นการดาวน์โหลดแยกต่างหาก ตัวอย่างเช่นกับไดเรกทอรีPackage-1และPackage-2ในPYTHONPATH,

Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py

ผู้ใช้ปลายทางสามารถและimport namespace.module1import namespace.module2

เป็นวิธีที่ดีที่สุดในการกำหนดแพ็คเกจเนมสเปซดังนั้นมากกว่าหนึ่งผลิตภัณฑ์งูหลามสามารถกำหนดโมดูลในเนมสเปซนั้นคืออะไร?


5
ดูเหมือนว่าฉันชอบ module1 และ module2 จริง ๆ แล้วเป็นแพ็คเกจย่อยมากกว่าโมดูล ตามที่ฉันเข้าใจแล้วโมดูลนั้นเป็นไฟล์เดียว บางที subpkg1 และ subpkg2 น่าจะเป็นชื่อมากกว่านี้ใช่ไหม
Alan

คำตอบ:


79

TL; DR:

ใน Python 3.3 คุณไม่ต้องทำอะไรเลยเพียงแค่ไม่ใส่__init__.pyในไดเรกทอรีแพ็คเกจของ namespace ของคุณและมันจะใช้งานได้ บน pre-3.3 เลือกpkgutil.extend_path()โซลูชันเหนือโซลูชันpkg_resources.declare_namespace()เนื่องจากเป็นการพิสูจน์ในอนาคตและเข้ากันได้กับแพ็คเกจเนมสเปซโดยนัยแล้ว


งูหลาม 3.3 เปิดตัวแพคเกจ namespace นัยดูPEP 420

ซึ่งหมายความว่าขณะนี้มีวัตถุสามประเภทที่สามารถสร้างโดยimport foo:

  • โมดูลที่แสดงโดยfoo.pyไฟล์
  • แพคเกจปกติแสดงโดยไดเรกทอรีfooที่มี__init__.pyไฟล์
  • แพ็กเกจเนมสเปซที่แสดงโดยหนึ่งหรือหลายไดเร็กทอรีfooโดยไม่มี__init__.pyไฟล์ใด ๆ

แพ็คเกจเป็นโมดูลด้วย แต่ที่นี่ฉันหมายถึง "โมดูลที่ไม่ใช่แพ็คเกจ" เมื่อฉันพูดว่า "โมดูล"

ก่อนอื่นก็สแกนsys.pathหาโมดูลหรือแพ็คเกจปกติ หากสำเร็จจะหยุดการค้นหาและสร้างและเริ่มต้นโมดูลหรือแพ็กเกจ หากไม่พบโมดูลหรือแพ็กเกจปกติ แต่พบอย่างน้อยหนึ่งไดเร็กทอรีมันจะสร้างและเริ่มต้นแพ็กเกจเนมสเปซ

โมดูลและแพ็คเกจปกติได้__file__ตั้งค่าเป็น.pyไฟล์ที่สร้างขึ้น แพ็คเกจปกติและเนมสเปซได้__path__ตั้งค่าเป็นไดเรกทอรีหรือไดเรกทอรีที่สร้างขึ้น

เมื่อคุณทำimport foo.bar, การค้นหาดังกล่าวข้างต้นที่เกิดขึ้นเป็นครั้งแรกสำหรับfooแล้วถ้าแพคเกจที่ถูกพบในการค้นหาbarจะทำกับเป็นเส้นทางการค้นหาแทนfoo.__path__ sys.pathหากfoo.barพบfooและfoo.barถูกสร้างและเริ่มต้น

ดังนั้นแพ็คเกจปกติและแพ็คเกจเนมสเปซผสมกันอย่างไร โดยปกติแล้วจะไม่ทำ แต่pkgutilวิธีการแพคเกจเนมสเปซเก่าที่ชัดเจนได้ถูกขยายเพื่อรวมแพคเกจเนมสเปซโดยนัย

หากคุณมีแพ็คเกจปกติที่มีอยู่ใน__init__.pyลักษณะนี้:

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

... ทำงานแบบเดิมคือการเพิ่มอื่น ๆปกติ__path__แพคเกจบนเส้นทางการสืบค้นของมัน แต่ใน Python 3.3 ก็เพิ่มแพ็คเกจเนมสเปซด้วย

ดังนั้นคุณสามารถมีโครงสร้างไดเรกทอรีต่อไปนี้:

├── path1
   └── package
       ├── __init__.py
       └── foo.py
├── path2
   └── package
       └── bar.py
└── path3
    └── package
        ├── __init__.py
        └── baz.py

... และตราบใดที่ทั้งสอง__init__.pyมีextend_pathสาย (และpath1, path2และpath3อยู่ในของคุณsys.path) import package.foo, import package.barและimport package.bazจะทำงานทั้งหมด

pkg_resources.declare_namespace(__name__) ยังไม่ได้รับการอัพเดตเพื่อรวมแพ็คเกจเนมสเปซโดยนัย


2
แล้ว setuptools ล่ะ ฉันต้องใช้namespace_packagesตัวเลือกนี้หรือไม่? และ__import__('pkg_resources').declare_namespace(__name__)สิ่งที่?
kawing-chiu

3
ฉันควรจะเพิ่มnamespace_packages=['package']ในsetup.py?
Laurent LAPORTE

1
@clacke: ด้วยnamespace_packages=['package']setup.py จะเพิ่ม a namespace_packages.txtใน EGG-INFO ยังไม่ทราบผลกระทบ ...
Laurent LAPORTE

1
@ kawing-Chiu สรรพคุณของpkg_resources.declare_namespaceกว่าก็คือว่ามันจะยังคงอยู่กับจอภาพpkgutil.extend_path sys.pathด้วยวิธีนี้หากมีการเพิ่มรายการใหม่sys.pathหลังจากแพ็คเกจในเนมสเปซถูกโหลดครั้งแรกแพคเกจในเนมสเปซในรายการเส้นทางใหม่นั้นจะยังคงสามารถโหลดได้ (ประโยชน์ของการใช้__import__('pkg_resources')เกินimport pkg_resourcesคือคุณไม่ได้pkg_resourcesรับการเปิดเผยmy_namespace_pkg.pkg_resources)
Arthur Tacca

1
@clacke มันใช้งานไม่ได้ (แต่มันมีผลเหมือนกับที่ทำ) มันจะเก็บรายชื่อทั้งหมดทั่วโลก namespaces sys.pathแพคเกจที่สร้างขึ้นด้วยฟังก์ชั่นที่และนาฬิกา เมื่อมีsys.pathการเปลี่ยนแปลงจะตรวจสอบว่ามีผลกระทบต่อ__path__ของ namespace ใด ๆ และถ้ามันจะปรับปรุง__path__คุณสมบัติเหล่านั้น
Arthur Tacca

81

มีโมดูลมาตรฐานที่เรียกว่าpkgutilซึ่งคุณสามารถ 'ผนวก' โมดูลในเนมสเปซที่กำหนดได้

ด้วยโครงสร้างไดเรกทอรีที่คุณระบุ:

Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py

คุณควรใส่ทั้งสองบรรทัดในทั้งสองPackage-1/namespace/__init__.pyและPackage-2/namespace/__init__.py(*):

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

(* เนื่องจาก - หากคุณไม่ได้ขึ้นอยู่กับการพึ่งพาระหว่างกัน - คุณไม่ทราบว่าจะต้องรู้จักใครก่อน - ดูPEP 420สำหรับข้อมูลเพิ่มเติม)

เป็นเอกสารประกอบระบุว่า:

สิ่งนี้จะเพิ่มใน__path__ไดเรกทอรีย่อยทั้งหมดของไดเรกทอรีบนsys.pathแพ็คเกจที่ตั้งชื่อตามแพ็คเกจ

จากนี้ไปคุณควรจะสามารถแจกจ่ายสองแพ็คเกจเหล่านี้ได้อย่างอิสระ


17
ข้อดีและข้อเสียของการใช้นั้นกับการนำเข้า __ ('pkg_resources') declare_namespace (__ ชื่อ ) คืออะไร
joeforker

14
อันดับแรก__import__ถือว่าเป็นรูปแบบที่ไม่ดีในกรณีนี้เนื่องจากสามารถแทนที่ได้อย่างง่ายดายด้วยคำสั่งการนำเข้าธรรมดา ตรงประเด็น pkg_resources เป็นไลบรารีที่ไม่ได้มาตรฐาน มันมาพร้อมกับ setuptools ดังนั้นมันจึงไม่ใช่ประเด็น googling อย่างรวดเร็วแสดงให้เห็นว่า pkgutil ถูกนำเสนอใน 2.5 และ pkg_resources ถือกำเนิดขึ้นมา อย่างไรก็ตาม pkgutil เป็นวิธีแก้ปัญหาที่รู้จักกันโดยทั่วไป pkg_resources รวมได้รับในความเป็นจริงปฏิเสธใน PEP 365
ไมค์ Hordecki

3
อ้างจากPEP 382 : แนวทางที่จำเป็นในปัจจุบันเพื่อแพ็คเกจ namespace ได้นำไปสู่กลไกที่เข้ากันไม่ได้เล็กน้อยหลายประการสำหรับการให้แพคเกจ namespace ตัวอย่างเช่น pkgutil รองรับไฟล์ * .pkg; setuptools ไม่ setuptools รองรับการตรวจสอบไฟล์ zip และสนับสนุนการเพิ่มบางส่วนให้กับตัวแปร _namespace_packages ในขณะที่ pkgutil ไม่รองรับ
Drake Guan

7
ไม่ควรใส่สองบรรทัดนี้ลงในไฟล์ทั้งสอง: Package-1/namespace/__init__.py และ Package-2/namespace/__init__.pyหากเราไม่ทราบว่าแพ็กเกจ dir ใดถูกระบุไว้ก่อน?
Bula

3
@ChristofferKarlsson ใช่นั่นคือประเด็นมันเป็นสิ่งที่ถูกต้องถ้าคุณรู้ว่าสิ่งใดเป็นครั้งแรก แต่คำถามที่แท้จริงคือคุณสามารถรับประกันได้ว่ามันจะเป็นครั้งแรกในสถานการณ์ใด ๆ เช่นสำหรับผู้ใช้อื่น ๆ ?
Bula

5

ส่วนนี้ควรอธิบายด้วยตนเอง

กล่าวโดยย่อให้ใส่รหัสเนมสเปซ__init__.pyอัปเดตsetup.pyเพื่อประกาศเนมสเปซและคุณสามารถไปได้ฟรี


9
คุณควรอ้างอิงส่วนที่เกี่ยวข้องของลิงค์เสมอในกรณีที่ลิงก์ที่เกี่ยวข้องเสียไป
Tinned_Tuna

2

นี่เป็นคำถามเก่า แต่มีคนแสดงความคิดเห็นในบล็อกของฉันเมื่อเร็ว ๆ นี้ว่าการโพสต์เกี่ยวกับแพ็คเกจเนมสเปซยังคงมีความเกี่ยวข้องดังนั้นฉันจึงคิดว่าฉันจะลิงค์ไปที่นี่เพราะมันเป็นตัวอย่างที่เป็นประโยชน์

https://web.archive.org/web/20150425043954/http://cdent.tumblr.com/post/216241761/python-namespace-packages-for-tiddlyweb

ลิงก์ไปยังบทความนี้สำหรับความกล้าหาญหลักของสิ่งที่เกิดขึ้น:

http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package

__import__("pkg_resources").declare_namespace(__name__)เคล็ดลับคือไดรฟ์สวยมากจัดการปลั๊กอินในTiddlyWebและป่านนี้ดูเหมือนว่าจะทำงานออก


-9

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

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

ดังนั้นในตัวอย่างสมมติว่า Package-1 และ Package-2 เป็นโฟลเดอร์ในระบบไฟล์ที่คุณใส่ไว้ในพา ธ Python คุณสามารถมีสิ่งต่อไปนี้:

Package-1/
  namespace/
  __init__.py
  module1.py
Package-2/
  namespace/
  __init__.py
  module2.py

ขณะนี้คุณมีหนึ่งแพคเกจnamespaceที่มีสองโมดูลและmodule1 module2และถ้าคุณไม่มีเหตุผลที่ดีคุณควรวางโมดูลไว้ในโฟลเดอร์และมีเฉพาะในเส้นทางของไพ ธ อนดังนี้

Package-1/
  namespace/
  __init__.py
  module1.py
  module2.py

ฉันกำลังพูดถึงสิ่งต่าง ๆ เช่นzope.xแพคเกจที่เกี่ยวข้องมากมายที่ถูกปล่อยออกมาเป็นดาวน์โหลดแยกต่างหาก
joeforker

ตกลง แต่อะไรคือผลกระทบที่คุณพยายามทำ หากโฟลเดอร์ที่มีแพ็คเกจที่เกี่ยวข้องทั้งหมดใน PYTHONPATH ล่าม Python จะค้นหามันให้คุณโดยที่คุณไม่ต้องใช้ความพยายามใด ๆ เพิ่มเติม
Tendayi Mawushe

5
หากคุณเพิ่มทั้ง Package-1 และ Package-2 ใน PYTHONPATH เฉพาะ P-Package / 1 namespace / เท่านั้นที่จะเห็นได้โดย Python
SørenLøvborg
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.