Empacotando pacotes de espaço de nomes#

Os pacotes de espaço de nomes permitem que você divida os subpacotes e módulos dentro de um único pacote em vários pacotes de distribuição (referidos como distribuições em este documento para evitar ambiguidade). Por exemplo, se você tiver a seguinte estrutura de pacote:

mynamespace/
    __init__.py
    subpackage_a/
        __init__.py
        ...
    subpackage_b/
        __init__.py
        ...
    module_b.py
pyproject.toml

E você usa este pacote em seu código assim:

from mynamespace import subpackage_a
from mynamespace import subpackage_b

Então você pode quebrar esses subpacotes em duas distribuições separadas:

mynamespace-subpackage-a/
    pyproject.toml
    src/
        mynamespace/
            subpackage_a/
                __init__.py

mynamespace-subpackage-b/
    pyproject.toml
    src/
        mynamespace/
            subpackage_b/
                __init__.py
            module_b.py

Cada subpacote pode agora ser instalado, usado e versionado separadamente.

Os pacotes de espaço de nomes podem ser úteis para uma grande coleção de pacotes vagamente relacionados (como um grande corpo de bibliotecas de cliente para vários produtos de uma única empresa). No entanto, os pacotes de espaço de nomes vêm com várias ressalvas e não são apropriados em todos os casos. Uma alternativa simples é usar um prefixo em todas as suas distribuições, como import mynamespace_subpackage_a (você pode até usar import mynamespace_subpackage_a as subpackage_a para manter o objeto de importação curto).

Criando um pacote de espaço de nomes#

There are currently two different approaches to creating namespace packages, from which the latter is discouraged:

  1. Usar pacotes de espaço de nomes nativos. Este tipo de pacote de espaço de nomes é definido na PEP 420 e está disponível em Python 3.3 e posterior. Isso é recomendado se os pacotes em seu espaço de nomes precisarem apenas oferecer suporte ao Python 3 e à instalação via pip.

  2. Use legacy namespace packages. This comprises pkgutil-style namespace packages and pkg_resources-style namespace packages.

Pacotes se espaço de nomes nativos#

Python 3.3 added implicit namespace packages from PEP 420. All that is required to create a native namespace package is that you just omit __init__.py from the namespace package directory. An example file structure (following src-layout):

mynamespace-subpackage-a/
    pyproject.toml # AND/OR setup.py, setup.cfg
    src/
        mynamespace/ # namespace package
            # No __init__.py here.
            subpackage_a/
                # Regular import packages have an __init__.py.
                __init__.py
                module.py

É extremamente importante que toda distribuição que usa o pacote de espaço de nomes omita o __init__.py ou use um __init__.py no estilo pkgutil. Se alguma distribuição não o fizer, isso fará com que a lógica do espaço de nomes falhe e os outros subpacotes não serão importáveis.

The src-layout directory structure allows automatic discovery of packages by most build backends. See layout src vs layout plano for more information. If however you want to manage exclusions or inclusions of packages yourself, this is possible to be configured in the top-level pyproject.toml:

[build-system]
...

[tool.setuptools.packages.find]
where = ["src/"]
include = ["mynamespace.subpackage_a"]

[project]
name = "mynamespace-subpackage-a"
...

The same can be accomplished with a setup.cfg:

[options]
package_dir =
    =src
packages = find_namespace:

[options.packages.find]
where = src

Or setup.py:

from setuptools import setup, find_namespace_packages

setup(
    name='mynamespace-subpackage-a',
    ...
    packages=find_namespace_packages(where='src/', include=['mynamespace.subpackage_a']),
    package_dir={'': 'src'},
)

Setuptools will search the directory structure for implicit namespace packages by default.

Um exemplo de trabalho completo de dois pacotes de espaço de nomes nativos pode ser encontrado no projeto exemplo de pacote de espaço de nomes nativo.

Nota

Como os pacotes de espaço de nomes nativos e no estilo pkgutil são amplamente compatíveis, você pode usar pacotes de espaço de nomes nativos nas distribuições que oferecem suporte apenas a Python 3 e pacotes de espaço de nomes no estilo pkgutil nas distribuições que precisam oferecer suporte a Python 2 e 3.

Legacy namespace packages#

These two methods, that were used to create namespace packages prior to PEP 420, are now considered to be obsolete and should not be used unless you need compatibility with packages already using this method. Also, pkg_resources has been deprecated.

To migrate an existing package, all packages sharing the namespace must be migrated simultaneously.

Aviso

Embora os pacotes de espaço de nomes nativos e pacotes de espaço de nomes no estilo pkgutil sejam amplamente compatíveis, os pacotes de espaço de nomes no estilo pkg_resources não são compatíveis com os outros métodos. Não é aconselhável usar métodos diferentes em distribuições diferentes que fornecem pacotes para o mesmo espaço de nomes.

Pacotes de espaço de nomes no estilo pkgutil#

Python 2.3 introduziu o módulo pkgutil e a função pkgutil.extend_path(). Isso pode ser usado para declarar pacotes de espaço de nomes que precisam ser compatíveis com Python 2.3+ e Python 3. Esta é a abordagem recomendada para o nível mais alto de compatibilidade.

Para criar um pacote de espaço de nomes no estilo pkgutil, você precisa fornecer um arquivo __init__.py para o pacote de espaço de nomes:

mynamespace-subpackage-a/
    src/
        pyproject.toml # AND/OR setup.cfg, setup.py
        mynamespace/
            __init__.py  # Namespace package __init__.py
            subpackage_a/
                __init__.py  # Regular package __init__.py
                module.py

The __init__.py file for the namespace package needs to contain the following:

__path__ = __import__('pkgutil').extend_path(__path__, __name__)

Every distribution that uses the namespace package must include such an __init__.py. If any distribution does not, it will cause the namespace logic to fail and the other sub-packages will not be importable. Any additional code in __init__.py will be inaccessible.

Um exemplo completamente funcional de dois pacotes de espaço de nomes no estilo pkgutil pode ser encontrado no projeto exemplo de espaço de nomes de pkgutil.

Pacotes de espaço de nomes no estilo pkg_resources#

Setuptools fornece a função pkg_resources.declare_namespace e o argumento namespace_packages para setup(). Juntos, eles podem ser usados para declarar pacotes de espaço de nomes. Embora essa abordagem não seja mais recomendada, ela está amplamente presente na maioria dos pacotes de espaço de nomes existentes. Se você estiver criando uma nova distribuição dentro de um pacote de espaço de nomes existente que usa esse método, é recomendável continuar usando-o, pois os diferentes métodos não são compatíveis entre si e não é aconselhável tentar migrar um pacote existente.

Para criar um pacote de espaço de nomes no estilo pkg_resources, você precisa fornecer um arquivo __init __. Py para o pacote de espaço de nomes:

mynamespace-subpackage-a/
    src/
        pyproject.toml # AND/OR setup.cfg, setup.py
        mynamespace/
            __init__.py  # Namespace package __init__.py
            subpackage_a/
                __init__.py  # Regular package __init__.py
                module.py

The __init__.py file for the namespace package needs to contain the following:

__import__('pkg_resources').declare_namespace(__name__)

Every distribution that uses the namespace package must include such an __init__.py. If any distribution does not, it will cause the namespace logic to fail and the other sub-packages will not be importable. Any additional code in __init__.py will be inaccessible.

Nota

Algumas recomendações mais antigas aconselham o seguinte no __init__.py de pacotes de espaço de nomes:

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

A ideia por trás disso era que, no caso raro de setuptools não estar disponível, os pacotes recorreriam aos pacotes no estilo pkgutil. Isso não é aconselhável porque os pacotes de espaço de nomes estilo pkgutil e pkg_resources não são compatíveis entre si. Se a presença de setuptools é uma preocupação, então o pacote deve depender explicitamente de setuptools via install_requires.

Finalmente, cada distribuição deve fornecer o argumento namespace_packages para setup() no setup.py. Por exemplo:

from setuptools import find_packages, setup

setup(
    name='mynamespace-subpackage-a',
    ...
    packages=find_packages()
    namespace_packages=['mynamespace']
)

Um exemplo de completamente funcional de dois pacotes de espaço de nomes no estilo pkg_resources pode ser encontrado no projeto exemplo de espaço de nomes de pkg_resources.