Come faccio a creare un pacchetto spazio dei nomi in Python?


141

In Python, un pacchetto dello spazio dei nomi consente di diffondere il codice Python tra diversi progetti. Ciò è utile quando si desidera rilasciare librerie correlate come download separati. Ad esempio, con le directory Package-1e Package-2in PYTHONPATH,

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

l'utente finale può import namespace.module1e import namespace.module2.

Qual è il modo migliore per definire un pacchetto dello spazio dei nomi in modo che più di un prodotto Python possa definire i moduli in quello spazio dei nomi?


5
Mi sembra che module1 e module2 siano in realtà subpackages piuttosto che moduli. A quanto ho capito, un modulo è fondamentalmente un singolo file. Forse subpkg1 e subpkg2 avrebbero più senso come nomi?
Alan,

Risposte:


79

TL; DR:

Su Python 3.3 non devi fare nulla, semplicemente non inserirli __init__.pynelle directory dei pacchetti del tuo spazio dei nomi e funzionerà. Su pre-3.3, scegli la pkgutil.extend_path()soluzione rispetto a pkg_resources.declare_namespace()quella, perché è a prova di futuro e già compatibile con i pacchetti di spazio dei nomi impliciti.


Python 3.3 introduce pacchetti di spazi dei nomi impliciti, vedi PEP 420 .

Ciò significa che ora ci sono tre tipi di oggetti che possono essere creati da un import foo:

  • Un modulo rappresentato da un foo.pyfile
  • Un pacchetto regolare, rappresentato da una directory foocontenente un __init__.pyfile
  • Un pacchetto dello spazio dei nomi, rappresentato da una o più directory foosenza alcun __init__.pyfile

Anche i pacchetti sono moduli, ma qui intendo "modulo non pacchetto" quando dico "modulo".

Prima sys.pathcerca un modulo o un pacchetto normale. Se riesce, interrompe la ricerca e crea e inizializza il modulo o il pacchetto. Se non trova alcun modulo o pacchetto normale, ma trova almeno una directory, crea e inizializza un pacchetto dello spazio dei nomi.

Moduli e pacchetti regolari si sono __file__impostati sul .pyfile da cui sono stati creati. I pacchetti regolari e dei namespace hanno __path__impostato la directory o le directory da cui sono stati creati.

Quando lo fai import foo.bar, la ricerca di cui sopra avviene per prima foo, quindi se viene trovato un pacchetto, la ricerca barviene eseguita foo.__path__come percorso di ricerca anziché sys.path. Se foo.barviene trovato fooe foo.barviene creato e inizializzato.

Quindi come si combinano i pacchetti regolari e i pacchetti dello spazio dei nomi? Normalmente no, ma il vecchio pkgutilmetodo esplicito del pacchetto dello spazio dei nomi è stato esteso per includere pacchetti impliciti dello spazio dei nomi.

Se hai un pacchetto regolare esistente che ha un __init__.pysimile:

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

... il comportamento precedente è quello di aggiungere altri pacchetti regolari sul percorso cercato al suo __path__. Ma in Python 3.3, aggiunge anche pacchetti di spazi dei nomi.

Quindi puoi avere la seguente struttura di directory:

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

... e fintanto che i due __init__.pyhanno le extend_pathlinee (e path1, path2e path3sono nella tua sys.path) import package.foo, import package.bare import package.bazfunzioneranno tutti.

pkg_resources.declare_namespace(__name__) non è stato aggiornato per includere i pacchetti di spazio dei nomi impliciti.


2
Che dire di setuptools? Devo usare l' namespace_packagesopzione? E la __import__('pkg_resources').declare_namespace(__name__)cosa?
kawing-chiu,

3
Devo aggiungere namespace_packages=['package']il setup.py?
Laurent LAPORTE,

1
@clacke: con namespace_packages=['package'], setup.py aggiungerà a namespace_packages.txtin EGG-INFO. Ancora non conosco gli impatti ...
Laurent LAPORTE il

1
@ kawing-chiu Il vantaggio di pkg_resources.declare_namespaceover pkgutil.extend_pathè che continuerà a monitorare sys.path. In questo modo, se un nuovo elemento viene aggiunto sys.pathdopo che un pacchetto nello spazio dei nomi è stato caricato per la prima volta, i pacchetti nello spazio dei nomi in quel nuovo elemento del percorso possono ancora essere caricati. (A vantaggio di usare __import__('pkg_resources')più di import pkg_resourcesè che non si finisce per pkg_resourcesessere esposti come my_namespace_pkg.pkg_resources.)
Arthur Tacca

1
@clacke Non funziona in questo modo (ma ha lo stesso effetto di se avesse funzionato). Mantiene un elenco globale di tutti gli spazi dei nomi dei pacchetti creati con quella funzione e controlla sys.path. Quando sys.pathcambia controlla se ciò influisce __path__su uno qualsiasi degli spazi dei nomi e, in caso affermativo, aggiorna tali __path__proprietà.
Arthur Tacca,

81

Esiste un modulo standard, chiamato pkgutil , con il quale è possibile "aggiungere" moduli a un determinato spazio dei nomi.

Con la struttura di directory che hai fornito:

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

Dovresti mettere queste due righe in entrambi Package-1/namespace/__init__.pye Package-2/namespace/__init__.py(*):

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

(* poiché, a meno che tu non dichiari una dipendenza tra di loro, non sai quale di essi verrà riconosciuto per primo, vedi PEP 420 per ulteriori informazioni)

Come dice la documentazione :

Ciò aggiungerà a __path__tutte le sottodirectory di directory sys.pathdel pacchetto che prendono il nome dal pacchetto.

D'ora in poi, dovresti essere in grado di distribuire quei due pacchetti in modo indipendente.


17
Quali sono i pro e i contro dell'utilizzo di versus import __ ('pkg_resources'). Declare_namespace (__ name )?
Joeforker,

14
In primo luogo, __import__in questo caso è considerato cattivo stile poiché può essere facilmente sostituito con una semplice dichiarazione di importazione. Più precisamente, pkg_resources è una libreria non standard. Viene fornito con setuptools, quindi non è un problema. La rapida ricerca su Google rivela che pkgutil è stato introdotto in 2.5 e pkg_resources lo precede. Tuttavia, pkgutil è una soluzione ufficialmente riconosciuta. L'inclusione di pkg_resources è stata, infatti, respinta in PEP 365.
Mike Hordecki,

3
Citazione da PEP 382 : L'attuale approccio imperativo ai pacchetti di spazi dei nomi ha portato a molteplici meccanismi leggermente incompatibili per la fornitura di pacchetti di spazi dei nomi. Ad esempio, pkgutil supporta file * .pkg; setuptools no. Allo stesso modo, setuptools supporta il controllo dei file zip e supporta l'aggiunta di porzioni alla sua variabile _namespace_packages, mentre pkgutil no.
Drake Guan,

7
Non dovrebbe queste due linee messe in entrambi i file: Package-1/namespace/__init__.py e Package-2/namespace/__init__.py condizione che non sappiamo che dir pacchetto è elencata per prima?
Bula,

3
@ChristofferKarlsson sì, questo è il punto, va bene se sai qual è il primo, ma la vera domanda è: puoi garantire che sarà il primo in ogni situazione, ad esempio per gli altri utenti?
Bula,


2

Questa è una vecchia domanda, ma qualcuno ha recentemente commentato sul mio blog che il mio post sui pacchetti dello spazio dei nomi era ancora pertinente, quindi ho pensato di collegarmi qui perché fornisce un esempio pratico di come farlo andare:

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

Questo si collega a questo articolo per le viscere principali di ciò che sta succedendo:

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

Il __import__("pkg_resources").declare_namespace(__name__)trucco sta praticamente nella gestione dei plugin in TiddlyWeb e finora sembra funzionare.


-9

Hai i concetti dello spazio dei nomi Python in primo piano, non è possibile in Python mettere i pacchetti in moduli. I pacchetti contengono moduli non viceversa.

Un pacchetto Python è semplicemente una cartella contenente un __init__.pyfile. Un modulo è qualsiasi altro file in un pacchetto (o direttamente sul PYTHONPATH) che ha .pyun'estensione. Quindi nel tuo esempio hai due pacchetti ma nessun modulo definito. Se si considera che un pacchetto è una cartella del file system e un modulo è un file, allora si vede perché i pacchetti contengono moduli e non viceversa.

Quindi, nel tuo esempio, supponendo che Package-1 e Package-2 siano cartelle nel file system che hai inserito nel percorso Python puoi avere quanto segue:

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

Ora hai un pacchetto namespacecon due moduli module1e module2. e a meno che tu non abbia una buona ragione, dovresti probabilmente mettere i moduli nella cartella e avere solo quello sul percorso di Python come di seguito:

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

Sto parlando di cose come il zope.xrilascio di un sacco di pacchetti correlati come download separati.
Joeforker,

Ok, ma qual è l'effetto che stai cercando di ottenere. Se le cartelle contenenti pacchetti correlati sono tutte su PYTHONPATH, l'interprete Python le troverà per te senza alcuno sforzo aggiuntivo da parte tua.
Tendayi Mawushe,

5
Se aggiungi sia Package-1 che Package-2 a PYTHONPATH, Python visualizzerà solo Package-1 / namespace /.
Søren Løvborg,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.