Como crio um pacote de namespace no Python?

141

No Python, um pacote de namespace permite espalhar o código Python entre vários projetos. Isso é útil quando você deseja liberar bibliotecas relacionadas como downloads separados. Por exemplo, com os diretórios Package-1e Package-2em PYTHONPATH,

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

o usuário final pode import namespace.module1e import namespace.module2.

Qual é a melhor maneira de definir um pacote de namespace para que mais de um produto Python possa definir módulos nesse namespace?

joeforker
fonte
5
Parece-me que module1 e module2 são na verdade subpacotes em vez de módulos. Pelo que entendi, um módulo é basicamente um único arquivo. Talvez subpkg1 e subpkg2 fizessem mais sentido como nomes?
Alan

Respostas:

79

TL; DR:

No Python 3.3, você não precisa fazer nada, apenas não coloque nenhum __init__.pyem seus diretórios de pacotes de espaço para nome e ele simplesmente funcionará. Na versão anterior à 3.3, escolha a pkgutil.extend_path()solução em vez de pkg_resources.declare_namespace()outra, porque ela é à prova do futuro e já é compatível com pacotes de espaço para nome implícitos.


O Python 3.3 apresenta pacotes implícitos de namespace, consulte PEP 420 .

Isso significa que agora existem três tipos de objetos que podem ser criados por um import foo:

  • Um módulo representado por um foo.pyarquivo
  • Um pacote regular, representado por um diretório que foocontém um __init__.pyarquivo
  • Um pacote de namespace, representado por um ou mais diretórios foosem nenhum __init__.pyarquivo

Pacotes também são módulos, mas aqui quero dizer "módulo sem pacote" quando digo "módulo".

Primeiro, ele procura sys.pathpor um módulo ou pacote regular. Se for bem-sucedido, ele interrompe a pesquisa e cria e inicializa o módulo ou pacote. Se não encontrou nenhum módulo ou pacote regular, mas encontrou pelo menos um diretório, ele cria e inicializa um pacote de namespace.

Módulos e pacotes regulares foram __file__definidos para o .pyarquivo de onde foram criados. Pacotes regulares e de namespace foram __path__definidos para o diretório ou diretórios dos quais foram criados.

Quando você faz isso import foo.bar, a pesquisa acima acontece primeiro e foo, se um pacote foi encontrado, a pesquisa baré feita com foo.__path__o caminho de pesquisa em vez de sys.path. Se foo.barfor encontrado fooe foo.barcriado e inicializado.

Então, como os pacotes regulares e os pacotes de namespace se misturam? Normalmente não, mas o pkgutilmétodo antigo do pacote de espaço para nome explícito foi estendido para incluir pacotes implícitos de espaço para nome.

Se você possui um pacote regular que possui uma __init__.pyaparência semelhante a esta:

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

... o comportamento herdado é adicionar outros pacotes regulares no caminho pesquisado ao seu __path__. Mas no Python 3.3, ele também adiciona pacotes de namespace.

Então você pode ter a seguinte estrutura de diretórios:

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

... e contanto que os dois __init__.pytenham as extend_pathlinhas (e path1, path2e path3estejam na sua sys.path) import package.foo, import package.bare import package.baztodos funcionem.

pkg_resources.declare_namespace(__name__) não foi atualizado para incluir pacotes implícitos de namespace.

clacke
fonte
2
E as ferramentas de configuração? Eu tenho que usar a namespace_packagesopção? E a __import__('pkg_resources').declare_namespace(__name__)coisa?
Kawing-chiu 10/10/16
3
Devo adicionar namespace_packages=['package']o setup.py?
Laurent LAPORTE 10/10
1
@clacke: Com namespace_packages=['package'], o setup.py adicionará um namespace_packages.txtno EGG-INFO. Ainda não sei os impactos ...
Laurent LAPORTE
1
@ kawing-chiu O benefício do pkg_resources.declare_namespaceexcesso pkgutil.extend_pathé que ele continuará a monitorar sys.path. Dessa forma, se um novo item for adicionado sys.pathapós o primeiro carregamento de um pacote no namespace, os pacotes no namespace nesse novo item de caminho ainda poderão ser carregados. (A vantagem de usar __import__('pkg_resources')mais import pkg_resourcesé que você não acabar pkg_resourcessendo exposto como my_namespace_pkg.pkg_resources.)
Arthur Tacca
1
@clacke Não funciona dessa maneira (mas tem o mesmo efeito que se tivesse). Ele mantém uma lista global de todos os namespaces de pacotes criados com essa função e observa sys.path. Quando sys.pathmuda, verifica se isso afeta __path__algum espaço para nome e, se o faz, atualiza essas __path__propriedades.
Arthur Tacca 12/01
81

Há um módulo padrão, chamado pkgutil , com o qual você pode 'anexar' módulos a um determinado espaço para nome.

Com a estrutura de diretórios que você forneceu:

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

Você deve colocar essas duas linhas em ambos Package-1/namespace/__init__.pye Package-2/namespace/__init__.py(*):

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

(* uma vez que, a menos que você declare uma dependência entre eles, você não sabe qual deles será reconhecido primeiro - consulte PEP 420 para obter mais informações)

Como a documentação diz:

Isso adicionará a __path__todos os subdiretórios de diretórios sys.pathdo pacote com o nome do pacote.

A partir de agora, você poderá distribuir esses dois pacotes independentemente.

Mike Hordecki
fonte
17
Quais são os prós e os contras de usar isso versus __ de importação ('pkg_resources'). Declare_namespace (__ name )?
5139 joeforker
14
Primeiro, __import__é considerado um estilo ruim neste caso, pois pode ser facilmente substituído por uma declaração de importação simples. Mais exatamente, pkg_resources é uma biblioteca não padrão. Ele vem com o setuptools, então isso não é um problema. A pesquisa rápida no Google revela que o pkgutil foi introduzido no 2.5 e o pkg_resources é anterior a ele. No entanto, o pkgutil é uma solução reconhecida oficialmente. pkg_resources inclusão foi, de fato, rejeitado em PEP 365.
Mike Hordecki
3
Citação do PEP 382 : A atual abordagem imperativa para os pacotes de namespace levou a vários mecanismos levemente incompatíveis para fornecer pacotes de namespace. Por exemplo, o pkgutil suporta arquivos * .pkg; setuptools não. Da mesma forma, o setuptools suporta a inspeção de arquivos zip e a adição de partes à sua variável _namespace_packages, enquanto o pkgutil não.
precisa saber é o seguinte
7
Essas duas linhas não devem ser colocadas nos dois arquivos: Package-1/namespace/__init__.py e Package-2/namespace/__init__.py desde que não saibamos qual diretório do pacote está listado primeiro?
Bula
3
@ChristofferKarlsson Sim, esse é o ponto, tudo bem se você souber qual é o primeiro, mas a verdadeira questão é: você pode garantir que será o primeiro em qualquer situação, ou seja, para outros usuários?
Bula
5

Esta seção deve ser bastante auto-explicativa.

Em resumo, insira o código do espaço para nome __init__.py, atualize setup.pypara declarar um espaço para nome e você estará livre para ir.

iElectric
fonte
9
Você sempre deve citar a parte relevante de um link, caso o link relevante fique inoperante.
Tinned_Tuna
2

Esta é uma pergunta antiga, mas alguém comentou recentemente no meu blog que minha postagem sobre pacotes de namespace ainda era relevante, então pensei em vincular a ele aqui, pois fornece um exemplo prático de como fazê-lo:

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

O link para este artigo mostra as principais entranhas do que está acontecendo:

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

O __import__("pkg_resources").declare_namespace(__name__)truque é basicamente impulsionar o gerenciamento de plugins no TiddlyWeb e até agora parece estar dando certo.

cdent
fonte
-9

Você tem seus conceitos de namespace do Python de trás para frente, não é possível no python colocar pacotes em módulos. Pacotes contêm módulos e não o contrário.

Um pacote Python é simplesmente uma pasta que contém um __init__.pyarquivo. Um módulo é qualquer outro arquivo em um pacote (ou diretamente no PYTHONPATH) que possui um.py extensão. Portanto, no seu exemplo, você tem dois pacotes, mas nenhum módulo definido. Se você considerar que um pacote é uma pasta do sistema de arquivos e um módulo é um arquivo, você verá por que os pacotes contêm módulos e não o contrário.

Portanto, no seu exemplo, assumindo que o Pacote 1 e o Pacote 2 sejam pastas no sistema de arquivos que você colocou no caminho do Python, você pode ter o seguinte:

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

Agora você tem um pacote namespacecom dois módulos module1e module2. e, a menos que você tenha um bom motivo, provavelmente deverá colocar os módulos na pasta e ter apenas isso no caminho do python, como abaixo:

Package-1/
  namespace/
  __init__.py
  module1.py
  module2.py
Tendayi Mawushe
fonte
Estou falando de coisas como zope.xonde um monte de pacotes relacionados é lançado como downloads separados.
5139 joeforker
Ok, mas qual é o efeito que você está tentando alcançar. Se as pastas que contêm todos os pacotes relacionados no PYTHONPATH, o interpretador Python os encontrará para você sem nenhum esforço extra de sua parte.
Tendayi Mawushe
5
Se você adicionar o Pacote-1 e o Pacote-2 ao PYTHONPATH, apenas o Pacote-1 / namespace / será visto pelo Python.
Søren Løvborg 21/07