Combinando vários pacotes em um único espaço para nome Python

Às vezes, torna-se necessário separar vários pacotes no mesmo espaço para nome em diferentes caminhos físicos. Por exemplo, se você deseja transferir diferentes layouts de plug-ins, pode adicioná-los posteriormente sem controlar sua localização e, ao mesmo tempo, acessá-los através de um espaço para nome.

Esta folha de dicas, mais adequada para iniciantes, é dedicada aos namespaces do Python.

Vejamos como isso pode ser feito em diferentes versões do Python, pois, embora o Python2 não seja mais suportado em breve, muitos de nós estamos neste momento entre dois incêndios, e essa é apenas uma das nuances importantes na transição.

imagem

Considere este exemplo:

Queremos obter a estrutura do pacote:

namespace1 package1 module1 package2 module2 

Conteúdo do arquivo Module1

 print('package 1') var1 = 1 

Conteúdo do arquivo Module2

 print('package 2') var2 = 2 

Ao mesmo tempo, os pacotes são distribuídos na seguinte estrutura de pastas:

  path1 namespace1 package1 module1 path2 namespace1 package2 module2 

Suponha que, de alguma forma, path1 e path2 já tenham sido adicionados ao sys.path. Precisamos acessar module1 e module2:

  from namespace1.package1 import module1 from namespace1.package2 import module2 

O que acontece no Python 3.7 quando esse código é executado? Tudo funciona maravilhosamente:

 package 1 package 2 

Com o PEP-420 no Python 3.3, o suporte para namespaces implícitos apareceu. Além disso, ao importar um pacote do py33, você não precisa criar arquivos __init__.py. E ao importar namespace, é apenas _ proibido. Se o arquivo __init__.py estiver presente em um ou nos dois diretórios com o nome name1, ocorrerá um erro na importação do segundo pacote.

 ModuleNotFoundError: No module named 'namespace1.package2' 

Assim, a presença de alguém interno determina explicitamente o pacote, e os pacotes não podem ser combinados, é uma entidade única. Se você estiver iniciando um novo projeto, independente do desenvolvimento antigo, e os pacotes forem instalados usando o pip, será necessário seguir esse método. No entanto, às vezes herdamos o código antigo, que também precisa ser mantido, pelo menos por um tempo, ou portado para uma nova versão.

Vamos para o Python 2.7 . Com esta versão já é mais interessante, primeiro você precisa adicionar __init__.py a cada diretório para criar pacotes, caso contrário, o intérprete simplesmente não reconhece o pacote nesse conjunto de arquivos. E, em seguida, escreva a declaração explícita do espaço para nome nos arquivos __init__ relacionados ao espaço para nome1; caso contrário, apenas o primeiro pacote será importado.

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

O que acontece com isso? Quando o intérprete atinge a primeira importação, um pacote com o mesmo nome é pesquisado em sys.path, ele está no caminho1 / namespace1 e o interpretador executa o caminho1 / namespace1 / __ init__.py. Não são realizadas pesquisas adicionais. No entanto, a própria função extend_path realiza uma pesquisa em todo sys.path, localiza todos os pacotes com o nome namespace1 e o nome interno e os adiciona à variável __path__ do pacote namespace1, usada para procurar pacotes filho nesse namespace.

Nos guias oficiais, é recomendável que as iniciais sejam as mesmas sempre que o namespace1 for colocado. De fato, eles podem estar vazios, exceto o primeiro, que é encontrado durante uma pesquisa em sys.path, na qual pkgutil.extend_path deve ser chamado, porque o restante não é executado. No entanto, é claro, é melhor que a chamada seja realmente verdadeira em todas as equipes, para não amarrar sua lógica "no caso" e não adivinhar qual equipe foi a primeira a executar, porque a ordem de pesquisa pode mudar. Pelo mesmo motivo, você não deve colocar outros arquivos lógicos __init__ na área variável.

Isso funcionará em versões futuras e esse código pode ser usado para escrever um código compatível , mas lembre-se de que você deve aderir ao método escolhido em todos os pacotes distribuídos. Se na versão 3 você colocar uma caixa de entrada em alguns pacotes em uma chamada para pkgutil.extend_path e deixar alguns sem uma caixa de entrada, isso não funcionará.
Além disso, essa opção também é adequada para o caso em que você planeja instalar usando o python setup.py install.

Outra maneira, que agora é considerada um pouco desatualizada, mas ainda pode ser encontrada muito onde:

 #namespace1/__init__.py __import__('pkg_resources').declare_namespace(__name__) 

O módulo pkg_resources vem com o pacote setuptools. Aqui, o significado é o mesmo que no pkgutil - é necessário que cada arquivo __init__ contenha a mesma declaração de espaço para nome em todos os locais do espaço para nome1 e nenhum outro código esteja presente. Ao mesmo tempo, é necessário registrar o espaço para nome namespace_packages = ['namespace1'] em setup.py. Uma descrição mais detalhada da criação de pacotes está além do escopo deste artigo.

Além disso, você pode frequentemente encontrar esse código

 try: __import__('pkg_resources').declare_namespace(__name__) except: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) 

Aqui a lógica é simples - se o setuptools não estiver instalado, usamos o pkgutil, que está incluído na biblioteca padrão.

Se você configurar um espaço para nome de uma dessas maneiras, poderá chamar outro de um módulo. Por exemplo, altere namespace1 / package2 / module2

 import namespace1.package1.module1 print(var1) 

E então veremos o que acontece se nomearmos por engano um novo pacote e um pacote existente e o envolvermos com o mesmo espaço de nome. Por exemplo, haverá dois pacotes em locais diferentes com o nome package1.

 namespace1 package1 module1 package1 module2 

Nesse caso, apenas o primeiro será importado e não haverá acesso ao módulo2. Pacotes não podem ser combinados.

 from namespace1.package1 import module1 from namespace1.package1 import module2 #>>ImportError: cannot import name module2 

Resumo:

  1. Para Python anterior a 3.3 e instalando com pip, é recomendável usar uma declaração implícita de namespace.
  2. No caso de suporte para as versões 2 e 3, bem como a instalação com o pip e python setup.py install, a opção com pkgutil é recomendada.
  3. A opção pkg_resources é recomendada se você precisar dar suporte a pacotes mais antigos usando esse método ou se o pacote deve ser protegido por zip.

Fontes:


Exemplos podem ser encontrados aqui .

Source: https://habr.com/ru/post/pt458432/


All Articles