Bagaimana cara membuat paket namespace dengan Python?

141

Dalam Python, paket namespace memungkinkan Anda untuk menyebarkan kode Python di antara beberapa proyek. Ini berguna ketika Anda ingin merilis perpustakaan terkait sebagai unduhan terpisah. Misalnya, dengan direktori Package-1dan Package-2di PYTHONPATH,

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

pengguna akhir dapat import namespace.module1dan import namespace.module2.

Apa cara terbaik untuk mendefinisikan paket namespace sehingga lebih dari satu produk Python dapat mendefinisikan modul di namespace itu?

joeforker
sumber
5
Sepertinya saya seperti module1 dan module2 sebenarnya subpackages daripada modules. Seperti yang saya pahami, modul pada dasarnya adalah satu file. Mungkin subpkg1 dan subpkg2 lebih masuk akal sebagai nama?
Alan

Jawaban:

79

TL; DR:

Pada Python 3.3 Anda tidak perlu melakukan apa-apa, hanya saja jangan memasukkan apa pun __init__.pydi direktori paket namespace Anda dan itu hanya akan berfungsi. Pada pra-3.3, pilih pkgutil.extend_path()solusinya pkg_resources.declare_namespace(), karena ini adalah bukti masa depan dan sudah kompatibel dengan paket namespace implisit.


Python 3.3 memperkenalkan paket namespace implisit, lihat PEP 420 .

Ini berarti sekarang ada tiga jenis objek yang dapat dibuat oleh import foo:

  • Modul diwakili oleh foo.pyfile
  • Paket reguler, diwakili oleh direktori yang fooberisi __init__.pyfile
  • Paket namespace, diwakili oleh satu atau lebih direktori footanpa __init__.pyfile

Paket adalah modul juga, tapi di sini maksud saya "modul non-paket" ketika saya mengatakan "modul".

Pertama memindai sys.pathmodul atau paket reguler. Jika berhasil, ia berhenti mencari dan membuat dan menginisialisasi modul atau paket. Jika tidak menemukan modul atau paket reguler, tetapi ia menemukan setidaknya satu direktori, itu membuat dan menginisialisasi paket namespace.

Modul dan paket reguler telah __file__diatur ke .pyfile tempat mereka dibuat. Paket reguler dan namespace telah __path__ditetapkan ke direktori atau direktori tempat mereka dibuat.

Ketika Anda melakukannya import foo.bar, pencarian di atas terjadi terlebih dahulu foo, kemudian jika sebuah paket ditemukan, pencarian bardilakukan dengan foo.__path__sebagai jalur pencarian alih-alih sys.path. Jika foo.barditemukan, foodan foo.bardibuat serta diinisialisasi.

Jadi bagaimana paket reguler dan paket namespace tercampur? Biasanya tidak, tetapi pkgutilmetode paket namespace eksplisit lama telah diperluas untuk menyertakan paket namespace implisit.

Jika Anda memiliki paket reguler yang sudah ada yang memiliki __init__.pyseperti ini:

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

... perilaku lawas adalah menambahkan paket reguler lain di jalur yang dicari ke __path__. Namun dalam Python 3.3, ia juga menambahkan paket namespace.

Jadi Anda dapat memiliki struktur direktori berikut:

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

... dan selama keduanya __init__.pymemiliki extend_pathgaris (dan path1, path2dan path3ada di Anda sys.path) import package.foo, import package.bardan import package.bazsemua akan bekerja.

pkg_resources.declare_namespace(__name__) belum diperbarui untuk menyertakan paket namespace implisit.

klak
sumber
2
Bagaimana dengan setuptools? Apakah saya harus menggunakan namespace_packagesopsi ini? Dan __import__('pkg_resources').declare_namespace(__name__)masalahnya?
kawing-chiu
3
Harus saya tambahkan namespace_packages=['package']di setup.py?
Laurent LAPORTE
1
@clacke: With namespace_packages=['package'], setup.py akan menambahkan namespace_packages.txtdalam EGG-INFO. Masih tidak tahu dampaknya ...
Laurent LAPORTE
1
@ kawing-chiu Manfaat pkg_resources.declare_namespacelebih dari itu pkgutil.extend_pathadalah akan terus memonitor sys.path. Dengan begitu, jika item baru ditambahkan ke sys.pathsetelah paket di namespace pertama kali dimuat maka paket di namespace di item jalur baru masih dapat dimuat. (Manfaat menggunakan __import__('pkg_resources')lebih dari itu import pkg_resourcesadalah bahwa Anda tidak berakhir pkg_resourcesterekspos sebagai my_namespace_pkg.pkg_resources.)
Arthur Tacca
1
@clacke Tidak berfungsi seperti itu (tetapi memiliki efek yang sama seperti jika melakukannya). Itu memelihara daftar global semua ruang nama paket yang dibuat dengan fungsi itu, dan jam tangan sys.path. Ketika sys.pathperubahan memeriksa apakah itu memengaruhi __path__namespace mana pun, dan jika itu terjadi maka ia memperbarui __path__properti tersebut.
Arthur Tacca
81

Ada modul standar, yang disebut pkgutil , yang dengannya Anda dapat 'menambahkan' modul ke namespace yang diberikan.

Dengan struktur direktori yang Anda berikan:

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

Anda harus meletakkan kedua baris di keduanya Package-1/namespace/__init__.pydan Package-2/namespace/__init__.py(*):

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

(* karena - kecuali Anda menyatakan ketergantungan di antara mereka - Anda tidak tahu yang mana yang akan dikenali lebih dulu - lihat PEP 420 untuk informasi lebih lanjut)

Seperti yang dikatakan dalam dokumentasi :

Ini akan menambah __path__semua subdirektori dari direktori pada sys.pathnama setelah paket.

Mulai sekarang, Anda harus dapat mendistribusikan kedua paket secara mandiri.

Mike Hordecki
sumber
17
Apa pro dan kontra penggunaannya dibandingkan impor __ ('pkg_resources'). Declare_namespace (__ name )?
joeforker
14
Pertama, __import__dianggap gaya yang buruk dalam hal ini karena dapat dengan mudah diganti dengan pernyataan impor biasa. Lebih penting lagi, pkg_resources adalah pustaka non-standar. Itu datang dengan setuptools, jadi itu bukan masalah. Googling cepat mengungkapkan bahwa pkgutil diperkenalkan pada 2.5 dan pkg_resources mendahului itu. Namun demikian, pkgutil adalah solusi yang diakui secara resmi. pkg_resources penyertaan, pada kenyataannya, ditolak di PEP 365.
Mike Hordecki
3
Kutipan dari PEP 382 : Pendekatan imperatif saat ini untuk paket namespace telah menyebabkan beberapa mekanisme yang sedikit tidak kompatibel untuk menyediakan paket namespace. Misalnya, pkgutil mendukung file * .pkg; setuptools tidak. Demikian juga, setuptools mendukung pemeriksaan file zip, dan mendukung penambahan porsi ke variabel _namespace_packages, sedangkan pkgutil tidak.
Drake Guan
7
Tidakkah seharusnya kedua baris ini dimasukkan ke dalam kedua file: Package-1/namespace/__init__.py dan Package-2/namespace/__init__.py asalkan kita tidak tahu paket Package mana yang didaftar pertama?
Bula
3
@ChristofferKarlsson ya itu intinya, tidak apa-apa jika Anda tahu mana yang pertama, tetapi pertanyaan sebenarnya adalah dapatkah Anda menjamin itu akan menjadi yang pertama dalam situasi apa pun, misalnya untuk pengguna lain?
Bula
5

Bagian ini harus cukup jelas.

Singkatnya, masukkan kode namespace __init__.py, perbarui setup.pyuntuk mendeklarasikan namespace, dan Anda bebas untuk pergi.

iElectric
sumber
9
Anda harus selalu mengutip bagian yang relevan dari tautan, jika tautan yang bersangkutan mati.
Tinned_Tuna
2

Ini adalah pertanyaan lama, tetapi seseorang baru-baru ini berkomentar di blog saya bahwa postingan saya tentang paket namespace masih relevan, jadi saya pikir saya akan menautkannya di sini karena memberikan contoh praktis bagaimana membuatnya:

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

Itu tautan ke artikel ini untuk nyali utama dari apa yang terjadi:

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

The __import__("pkg_resources").declare_namespace(__name__)trick cukup banyak drive pengelolaan plugin di TiddlyWeb dan sejauh tampaknya akan bekerja keluar.

cdent
sumber
-9

Anda memiliki konsep namespace Python Anda kembali ke depan, tidak mungkin dalam python untuk meletakkan paket ke dalam modul. Paket berisi modul bukan sebaliknya.

Paket Python hanyalah sebuah folder yang berisi __init__.pyfile. Modul adalah file lain apa pun dalam suatu paket (atau langsung pada PYTHONPATH) yang memiliki .pyekstensi. Jadi, dalam contoh Anda, Anda memiliki dua paket tetapi tidak ada modul yang ditentukan. Jika Anda menganggap bahwa paket adalah folder sistem file dan modul adalah file maka Anda melihat mengapa paket berisi modul dan bukan sebaliknya.

Jadi dalam contoh Anda dengan asumsi Package-1 dan Package-2 adalah folder pada sistem file yang telah Anda letakkan di jalur Python Anda dapat memiliki yang berikut ini:

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

Anda sekarang memiliki satu paket namespacedengan dua modul module1dan module2. dan kecuali Anda memiliki alasan yang baik Anda mungkin harus meletakkan modul di folder dan hanya memiliki itu di jalur python seperti di bawah ini:

Package-1/
  namespace/
  __init__.py
  module1.py
  module2.py
Tendayi Mawushe
sumber
Saya sedang berbicara tentang hal-hal seperti di zope.xmana banyak paket terkait dirilis sebagai unduhan terpisah.
joeforker
Ok, tapi apa efek yang ingin Anda capai. Jika folder yang berisi semua paket terkait di PYTHONPATH, juru bahasa Python akan menemukannya untuk Anda tanpa upaya ekstra dari pihak Anda.
Tendayi Mawushe
5
Jika Anda menambahkan Package-1 dan Package-2 ke PYTHONPATH, hanya Package-1 / namespace / yang akan dilihat oleh Python.
Søren Løvborg