Làm cách nào để tạo gói không gian tên trong Python?


141

Trong Python, gói không gian tên cho phép bạn trải đều mã Python giữa một số dự án. Điều này hữu ích khi bạn muốn phát hành các thư viện liên quan dưới dạng tải xuống riêng biệt. Ví dụ, với các thư mục Package-1Package-2trong PYTHONPATH,

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

người dùng cuối có thể import namespace.module1import namespace.module2.

Cách tốt nhất để xác định gói không gian tên là gì để nhiều sản phẩm Python có thể xác định các mô-đun trong không gian tên đó?


5
Theo tôi thì mô-đun 1 và mô-đun 2 thực sự là các gói con hơn là các mô-đun. Theo tôi hiểu, một mô-đun về cơ bản là một tệp duy nhất. Có lẽ subpkg1 và subpkg2 sẽ có ý nghĩa hơn như tên?
Alan

Câu trả lời:


79

TL; DR:

Trên Python 3.3 bạn không phải làm gì cả, chỉ cần không đặt bất kỳ thứ gì __init__.pyvào thư mục gói không gian tên của bạn và nó sẽ chỉ hoạt động. Vào trước 3.3, hãy chọn pkgutil.extend_path()giải pháp thay vì giải pháp pkg_resources.declare_namespace()tương lai và đã tương thích với các gói không gian tên ẩn.


Python 3.3 giới thiệu các gói không gian tên ẩn, xem PEP 420 .

Điều này có nghĩa là hiện có ba loại đối tượng có thể được tạo bởi import foo:

  • Một mô-đun được đại diện bởi một foo.pytập tin
  • Một gói thông thường, được đại diện bởi một thư mục foochứa một __init__.pytệp
  • Một gói không gian tên, được đại diện bởi một hoặc nhiều thư mục foomà không có bất kỳ __init__.pytệp nào

Các gói cũng là các mô-đun, nhưng ở đây tôi có nghĩa là "mô-đun không gói" khi tôi nói "mô-đun".

Đầu tiên, nó quét sys.pathmột mô-đun hoặc gói thông thường. Nếu thành công, nó dừng tìm kiếm và tạo và kích hoạt mô-đun hoặc gói. Nếu nó không tìm thấy mô-đun hoặc gói thông thường, nhưng nó tìm thấy ít nhất một thư mục, nó sẽ tạo và khởi tạo gói không gian tên.

Các mô-đun và gói thông thường đã __file__được đặt thành .pytệp mà chúng được tạo từ đó. Các gói thông thường và không gian tên đã __path__được đặt thành thư mục hoặc thư mục mà chúng được tạo từ đó.

Khi bạn thực hiện import foo.bar, tìm kiếm ở trên xảy ra trước tiên foo, sau đó nếu một gói được tìm thấy, việc tìm kiếm barđược thực hiện với foo.__path__tư cách là đường dẫn tìm kiếm thay vì sys.path. Nếu foo.barđược tìm thấy, foofoo.barđược tạo và khởi tạo.

Vậy làm thế nào để các gói thông thường và các gói không gian tên trộn lẫn? Thông thường họ không, nhưng pkgutilphương thức gói không gian tên rõ ràng cũ đã được mở rộng để bao gồm các gói không gian tên ẩn.

Nếu bạn có một gói thông thường hiện có __init__.pynhư thế này:

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

... hành vi kế thừa là thêm bất kỳ gói thông thường nào khác trên đường dẫn tìm kiếm vào nó __path__. Nhưng trong Python 3.3, nó cũng thêm các gói không gian tên.

Vì vậy, bạn có thể có cấu trúc thư mục sau:

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

... và miễn là cả hai __init__.pycó các extend_pathdòng (và path1, path2path3nằm trong của bạn sys.path) import package.foo, import package.barimport package.baztất cả sẽ hoạt động.

pkg_resources.declare_namespace(__name__) đã không được cập nhật để bao gồm các gói không gian tên ẩn.


2
Thế còn setuptools? Tôi có phải sử dụng namespace_packagestùy chọn không? Và __import__('pkg_resources').declare_namespace(__name__)điều?
kawing-chiu

3
Tôi có nên thêm namespace_packages=['package']vào setup.py?
Laurent LAPORTE

1
@clacke: Với namespace_packages=['package'], setup.py sẽ thêm một namespace_packages.txttrong EGG-INFO. Vẫn không biết tác động của
LỚP

1
@ kawing-chiu Lợi ích của pkg_resources.declare_namespacehơn pkgutil.extend_pathlà nó sẽ tiếp tục theo dõi sys.path. Theo cách đó, nếu một mục mới được thêm vào sys.pathsau khi một gói trong không gian tên được tải trước thì các gói trong không gian tên trong mục đường dẫn mới đó vẫn có thể được tải. (Một lợi ích của việc sử dụng __import__('pkg_resources')hơn import pkg_resourceslà cuối cùng bạn không pkg_resourcesbị lộ my_namespace_pkg.pkg_resources.)
Arthur Tacca

1
@clacke Nó không hoạt động theo cách đó (nhưng nó có tác dụng tương tự như khi nó làm). Nó duy trì một danh sách toàn cầu về tất cả các không gian tên gói được tạo với chức năng đó và đồng hồ sys.path. Khi sys.paththay đổi, nó sẽ kiểm tra xem điều đó có ảnh hưởng đến __path__bất kỳ không gian tên nào không và nếu có thì nó sẽ cập nhật các __path__thuộc tính đó.
Arthur Tacca

81

Có một mô-đun chuẩn, được gọi là pkgutil , trong đó bạn có thể 'nối' các mô-đun vào một không gian tên nhất định.

Với cấu trúc thư mục bạn đã cung cấp:

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

Bạn nên đặt hai dòng đó ở cả hai Package-1/namespace/__init__.pyPackage-2/namespace/__init__.py(*):

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

(* kể từ khi bạn nêu ra sự phụ thuộc giữa họ - bạn không biết ai trong số họ sẽ được nhận ra trước - xem PEP 420 để biết thêm thông tin)

Như tài liệu nói:

Điều này sẽ thêm vào __path__tất cả các thư mục con của thư mục sys.pathđược đặt tên theo gói.

Từ bây giờ, bạn sẽ có thể phân phối hai gói đó một cách độc lập.


17
Những ưu và nhược điểm của việc sử dụng so với nhập __ ('pkg_resource'). Khai báo_namespace (tên __ ) là gì?
joeforker

14
Đầu tiên, __import__được coi là phong cách xấu trong trường hợp này vì nó có thể dễ dàng thay thế bằng câu lệnh nhập đơn giản. Hơn nữa, pkg_resource là một thư viện không chuẩn. Mặc dù vậy, nó đi kèm với setuptools, vì vậy đó không phải là vấn đề. Googling nhanh chóng tiết lộ rằng pkgutil đã được giới thiệu trong 2.5 và pkg_resource có trước nó. Tuy nhiên, pkgutil là một giải pháp được công nhận chính thức. Trên thực tế, pkg_resource đã bị từ chối trong PEP 365.
Mike Hordecki

3
Trích dẫn từ PEP 382 : Cách tiếp cận bắt buộc hiện tại đối với các gói không gian tên đã dẫn đến nhiều cơ chế không tương thích một chút để cung cấp các gói không gian tên. Ví dụ: pkgutil hỗ trợ các tệp * .pkg; setuptools không. Tương tự, setuptools hỗ trợ kiểm tra các tệp zip và hỗ trợ thêm các phần vào biến _namespace_packages của nó, trong khi pkgutil thì không.
Drake Guan

7
Không nên đặt hai dòng này vào cả hai tệp: Package-1/namespace/__init__.py Package-2/namespace/__init__.py cung cấp rằng chúng tôi không biết gói dir nào được liệt kê đầu tiên?
Bula

3
@ChristofferKarlsson vâng, đó là vấn đề, mọi thứ đều ổn nếu bạn biết cái nào là đầu tiên, nhưng câu hỏi thực sự là bạn có thể đảm bảo rằng nó sẽ là cái đầu tiên trong mọi tình huống, tức là cho những người dùng khác không?
Bula

5

Phần này sẽ khá tự giải thích.

Nói tóm lại, hãy đặt mã không gian tên vào __init__.py, cập nhật setup.pyđể khai báo một không gian tên và bạn có thể tự do đi.


9
Bạn phải luôn trích dẫn phần có liên quan của một liên kết, trong trường hợp liên kết có liên quan bị chết.
Tinned_Tuna

2

Đây là một câu hỏi cũ, nhưng gần đây ai đó đã nhận xét trên blog của tôi rằng việc tôi đăng các gói không gian tên vẫn có liên quan, vì vậy tôi nghĩ rằng tôi sẽ liên kết với nó ở đây vì nó cung cấp một ví dụ thực tế về cách thực hiện:

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

Liên kết đến bài viết này cho can đảm chính của những gì đang diễn ra:

http://www.siafoo.net/article/77#multipl-distribution-one-virtual-package

Thủ __import__("pkg_resources").declare_namespace(__name__)thuật này khá nhiều thúc đẩy việc quản lý các plugin trong TiddlyWeb và do đó dường như đã được giải quyết.


-9

Bạn có các khái niệm không gian tên Python của bạn trở lại phía trước, python không thể đặt các gói vào các mô-đun. Các gói chứa các mô-đun không theo cách khác.

Gói Python chỉ đơn giản là một thư mục chứa __init__.pytệp. Một mô-đun là bất kỳ tệp nào khác trong một gói (hoặc trực tiếp trên PYTHONPATH) có .pyphần mở rộng. Vì vậy, trong ví dụ của bạn, bạn có hai gói nhưng không có mô-đun xác định. Nếu bạn cho rằng một gói là một thư mục hệ thống tệp và một mô-đun là tệp thì bạn sẽ thấy tại sao các gói chứa các mô-đun chứ không phải theo cách khác.

Vì vậy, trong ví dụ của bạn giả sử Gói-1 và Gói-2 là các thư mục trên hệ thống tệp mà bạn đã đặt trên đường dẫn Python, bạn có thể có các mục sau:

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

Bây giờ bạn có một gói namespacevới hai mô-đun module1module2. và trừ khi bạn có một lý do chính đáng, có lẽ bạn nên đặt các mô-đun vào thư mục và chỉ có điều đó trên đường dẫn python như dưới đây:

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

Tôi đang nói về những thứ như zope.xnơi một loạt các gói liên quan được phát hành dưới dạng các bản tải xuống riêng biệt.
joeforker

Ok, nhưng hiệu quả bạn đang cố gắng để đạt được là gì. Nếu tất cả các thư mục chứa các gói liên quan trên PYTHONPATH, trình thông dịch Python sẽ tìm thấy chúng cho bạn mà không cần nỗ lực thêm về phía bạn.
Tendayi Mawushe

5
Nếu bạn thêm cả Gói-1 và Gói-2 vào PYTHONPATH, chỉ có Gói-1 / không gian tên / sẽ được Python nhìn thấy.
Søren Løvborg
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.