Jak utworzyć pakiet przestrzeni nazw w Pythonie?

141

W Pythonie pakiet przestrzeni nazw umożliwia rozpowszechnianie kodu Pythona między kilkoma projektami. Jest to przydatne, gdy chcesz zwolnić powiązane biblioteki jako osobne pliki do pobrania. Na przykład, w katalogach Package-1i Package-2na PYTHONPATH,

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

użytkownik końcowy może import namespace.module1i import namespace.module2.

Jaki jest najlepszy sposób zdefiniowania pakietu przestrzeni nazw, aby więcej niż jeden produkt Pythona mógł definiować moduły w tej przestrzeni nazw?

joeforker
źródło
5
Wydaje mi się, że module1 i module2 są w rzeczywistości pod-pakietami, a nie modułami. Jak rozumiem, moduł to w zasadzie pojedynczy plik. Może subpkg1 i subpkg2 miałyby więcej sensu jako nazwy?
Alan

Odpowiedzi:

79

TL; DR:

W Pythonie 3.3 nie musisz nic robić, po prostu nie umieszczaj żadnych __init__.pyw katalogach pakietów przestrzeni nazw i po prostu zadziała. W wersjach wcześniejszych niż 3.3 wybierz pkgutil.extend_path()rozwiązanie zamiast tego pkg_resources.declare_namespace(), ponieważ jest przyszłościowe i już jest zgodne z niejawnymi pakietami przestrzeni nazw.


Python 3.3 wprowadza niejawne pakiety przestrzeni nazw, zobacz PEP 420 .

Oznacza to, że istnieją teraz trzy typy obiektów, które można utworzyć za pomocą import foo:

  • Moduł reprezentowany przez foo.pyplik
  • Zwykły pakiet reprezentowany przez katalog foozawierający __init__.pyplik
  • Pakiet przestrzeni nazw, reprezentowany przez jeden lub więcej katalogów foobez żadnych __init__.pyplików

Pakiety też są modułami, ale mam tu na myśli „moduł nie będący pakietem”, kiedy mówię „moduł”.

Najpierw skanuje w sys.pathposzukiwaniu modułu lub zwykłego pakietu. Jeśli się powiedzie, zatrzymuje wyszukiwanie i tworzy oraz inicjalizuje moduł lub pakiet. Jeśli nie znalazł modułu lub zwykłego pakietu, ale znalazł przynajmniej jeden katalog, tworzy i inicjalizuje pakiet przestrzeni nazw.

Moduły i zwykłe pakiety mają __file__ustawiony .pyplik, z którego zostały utworzone. Pakiety zwykłe iz przestrzeni nazw __path__ustawiły katalog lub katalogi, z których zostały utworzone.

Kiedy to zrobisz import foo.bar, powyższe wyszukiwanie odbywa się najpierw dla foo, a następnie, jeśli pakiet został znaleziony, wyszukiwanie barjest wykonywane foo.__path__za pomocą ścieżki wyszukiwania zamiast sys.path. Jeśli foo.barzostanie znaleziony, fooi foo.barsą tworzone i inicjalizowane.

Jak więc mieszają się zwykłe pakiety i pakiety przestrzeni nazw? Zwykle tak nie jest, ale stara pkgutiljawna metoda pakietu przestrzeni nazw została rozszerzona o niejawne pakiety przestrzeni nazw.

Jeśli masz istniejący zwykły pakiet, który ma taki __init__.pywygląd:

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

... starsze zachowanie polega na dodaniu innych zwykłych pakietów na przeszukiwanej ścieżce do ich __path__. Ale w Pythonie 3.3 dodaje również pakiety przestrzeni nazw.

Możesz więc mieć następującą strukturę katalogów:

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

... i tak długo, jak obaj __init__.pymają extend_pathlinie (i path1, path2i path3są w twoim sys.path) import package.foo, import package.bari import package.bazwszystko będzie działać.

pkg_resources.declare_namespace(__name__) nie został zaktualizowany w celu uwzględnienia niejawnych pakietów przestrzeni nazw.

clacke
źródło
2
A co z setuptools? Czy muszę skorzystać z tej namespace_packagesopcji? A __import__('pkg_resources').declare_namespace(__name__)co?
kawing-chiu
3
Dodam namespace_packages=['package']w setup.py?
Laurent LAPORTE
1
@clacke: Za pomocą namespace_packages=['package']setup.py doda a namespace_packages.txtdo EGG-INFO. Nadal nie znam skutków…
Laurent LAPORTE,
1
@ kawing-chiu Zaletą pkg_resources.declare_namespaceover pkgutil.extend_pathjest to, że będzie nadal monitorować sys.path. W ten sposób, jeśli nowy element zostanie dodany sys.pathpo pierwszym załadowaniu pakietu w przestrzeni nazw, pakiety w przestrzeni nazw w tym nowym elemencie ścieżki nadal będą mogły być ładowane. (Zaletą używania __import__('pkg_resources')nadwyżki import pkg_resourcesjest to, że nie pkg_resourceszostaniesz ujawniony jako my_namespace_pkg.pkg_resources.)
Arthur Tacca
1
@clacke To nie działa w ten sposób (ale ma taki sam efekt, jak gdyby działało). Utrzymuje globalną listę wszystkich przestrzeni nazw pakietów utworzonych za pomocą tej funkcji i obserwuje sys.path. Po sys.pathzmianach sprawdza, czy ma to wpływ na __path__jakąkolwiek przestrzeń nazw, a jeśli tak, aktualizuje te __path__właściwości.
Arthur Tacca
81

Istnieje standardowy moduł o nazwie pkgutil , za pomocą którego można „dołączać” moduły do ​​danej przestrzeni nazw.

Dzięki podanej strukturze katalogów:

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

Powinieneś umieścić te dwie linie w obu Package-1/namespace/__init__.py i Package-2/namespace/__init__.py(*):

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

(* ponieważ - jeśli nie określisz zależności między nimi - nie wiesz, które z nich zostaną rozpoznane jako pierwsze - zobacz PEP 420, aby uzyskać więcej informacji)

Jako dokumentacja mówi :

To doda do pakietu __path__wszystkie podkatalogi katalogów wsys.path nazwanych tak jak pakiet.

Od teraz powinieneś móc dystrybuować te dwa pakiety niezależnie.

Mike Hordecki
źródło
17
Jakie są zalety i wady korzystania z tego w porównaniu z importem __ („pkg_resources”). Decre_namespace (__ name )?
joeforker
14
Po pierwsze, __import__w tym przypadku jest uważany za zły styl, ponieważ można go łatwo zastąpić zwykłą instrukcją importu. Co więcej, pkg_resources jest biblioteką niestandardową. Jest dostarczany z narzędziami konfiguracyjnymi, więc nie stanowi to problemu. Szybkie wyszukiwanie w Google ujawnia, że ​​pkgutil został wprowadzony w wersji 2.5, a pkg_resources jest starszy niż. Niemniej jednak pkgutil jest oficjalnie uznanym rozwiązaniem. Włączenie pkg_resources zostało w rzeczywistości odrzucone w PEP 365.
Mike Hordecki
3
Cytat z PEP 382 : Obecne imperatywne podejście do pakietów przestrzeni nazw doprowadziło do wielu nieco niekompatybilnych mechanizmów dostarczania pakietów przestrzeni nazw. Na przykład pkgutil obsługuje pliki * .pkg; setuptools nie. Podobnie, setuptools obsługuje sprawdzanie plików zip i obsługuje dodawanie części do swojej zmiennej _namespace_packages, podczas gdy pkgutil nie.
Drake Guan
7
Czy te dwie linie nie powinny być umieszczone w obu plikach: Package-1/namespace/__init__.py i pod Package-2/namespace/__init__.py warunkiem, że nie wiemy, który katalog Package jest wymieniony jako pierwszy?
Bula,
3
@ChristofferKarlsson tak, o to chodzi, wszystko w porządku, jeśli wiesz, co jest pierwsze, ale prawdziwe pytanie brzmi: czy możesz zagwarantować, że będzie to pierwsze w każdej sytuacji, tj. Dla innych użytkowników?
Bula
5

Ta sekcja powinna być dość oczywista.

Krótko mówiąc, umieść kod przestrzeni nazw __init__.py, zaktualizuj, setup.pyaby zadeklarować przestrzeń nazw i możesz zaczynać.

iElectric
źródło
9
Zawsze należy cytować odpowiednią część linku, na wypadek gdyby odnośny link zniknął.
Tinned_Tuna
2

To stare pytanie, ale ktoś niedawno skomentował na moim blogu, że mój post dotyczący pakietów przestrzeni nazw jest nadal aktualny, więc pomyślałem, że umieściłbym do niego link, ponieważ zawiera praktyczny przykład, jak to zrobić:

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

To prowadzi do tego artykułu, aby poznać główne aspekty tego, co się dzieje:

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

__import__("pkg_resources").declare_namespace(__name__)Trik jest dość dużo napędza zarządzania wtyczek w TiddlyWeb i do tej pory wydaje się być obecnie pracuje.

cdent
źródło
-9

Masz koncepcje przestrzeni nazw Pythona od początku do końca, w Pythonie nie jest możliwe umieszczanie pakietów w modułach. Pakiety zawierają moduły, a nie na odwrót.

Pakiet Pythona to po prostu folder zawierający __init__.pyplik. Moduł to dowolny inny plik w pakiecie (lub bezpośrednio w PYTHONPATH), który ma .pyrozszerzenie. W twoim przykładzie masz dwa pakiety, ale nie zdefiniowano żadnych modułów. Jeśli weźmiesz pod uwagę, że pakiet to folder systemu plików, a moduł to plik, zobaczysz, dlaczego pakiety zawierają moduły, a nie na odwrót.

W twoim przykładzie zakładając, że Package-1 i Package-2 są folderami w systemie plików, które umieściłeś w ścieżce Pythona, możesz mieć następujące rzeczy:

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

Masz teraz jeden pakiet namespacez dwoma modułami module1i module2. i jeśli nie masz dobrego powodu, prawdopodobnie powinieneś umieścić moduły w folderze i mieć tylko to na ścieżce Pythona, jak poniżej:

Package-1/
  namespace/
  __init__.py
  module1.py
  module2.py
Tendayi Mawushe
źródło
Mówię o takich rzeczach, jak zope.xkilka powiązanych pakietów wydawanych jako osobne pliki do pobrania.
joeforker
Ok, ale jaki efekt próbujesz osiągnąć. Jeśli foldery zawierające wszystkie powiązane pakiety w PYTHONPATH, interpreter Pythona znajdzie je dla Ciebie bez dodatkowego wysiłku z Twojej strony.
Tendayi Mawushe
5
Jeśli dodasz zarówno Package-1, jak i Package-2 do PYTHONPATH, Python będzie widział tylko Package-1 / namespace /.
Søren Løvborg