Как сохранить имена подмодулей из пространства имен пакета Python?

Я хочу, чтобы интерфейс некоторого модуля содержал определенное количество функций и классов (и ничего больше). Я мог бы реализовать все из них в одном файле и легко получить интерфейс, который я хочу. Но так как есть много кода, я бы предпочел разбить все на несколько файлов, скажем

mypackage/
    __init__.py
    a.py
    b.py
    c.py
    d.py

Чтобы получить желаемый интерфейс, я определяю файл __init__.py для пакета, который импортирует все общедоступные символы из a, b, c и d:

from a import func_a1, func_a2, ClassA1, ClassA2
from b import func_b1, func_b2, ClassB1, ClassB2
from c import func_c1, func_c2, ClassC1, ClassC2
from d import func_d1, func_d2, ClassD1, ClassD2

Если я импортирую пакет, используя

import mypackage

пространство имен пакетов также содержит символы a, b, c и d. Эти имена являются деталями реализации, а не частью моего интерфейса. Я не хочу, чтобы они отображались как "общедоступные" символы. Каков наилучший способ избавиться от них?

Параметры, которые я рассмотрел,

  • Используйте только один модуль вместо пакета. Интерфейс будет выглядеть отлично, но реализация станет менее понятной, чем сейчас.

  • Добавьте строку

    del a, b, c, d
    

    до конца __init__.py. Работает нормально, но кажется взломанным. (Например, вы больше не можете import __init__, который работает без этой строки.)

  • Переименуйте a, b, c и d в _a, _b, _c и _d. Теперь они включены в пространство имен mypackage как символы "private", с которыми я в порядке, но мне кажется странным, что все мои имена файлов начинаются с подчеркивания (на самом деле, конечно, более четырех подмодулей).

Любые лучшие предложения? Или мысли о том, какой вариант предпочтительнее?

Или я просто анал и не должен заботиться обо всем этом?

Ответ 1

Если вы действительно хотите удалить имена из пространства имен, вы можете просто использовать инструкцию del на них, и они исчезнут, как ветер.

Ответ 2

Если некоторые из файлов в пакете действительно являются деталями реализации, продолжайте и придерживайте знак подчеркивания перед ними - это то, для чего мы их используем.

Например, если вы посмотрите в ctypes, вы увидите

__init__.py
==================================================
"""create and manipulate C data types in Python"""

import os as _os, sys as _sys

__version__ = "1.1.0"

from _ctypes import Union, Structure, Array
from _ctypes import _Pointer
from _ctypes import CFuncPtr as _CFuncPtr
...

Как вы можете видеть, даже os и sys стали деталями реализации в этом файле.

Ответ 3

Из http://docs.python.org/tutorial/modules.html:

В заявлении import используется следующее соглашение: если пакеты __init__.py определяет список с именем __all__, он считается списком имен модулей, которые должны быть импортированы когда из импорта пакета * встречаются.

В своем mypackage/__init__.py попробуйте добавить это:

# add this line, replace "..." with the rest of the definitions
# you want made public
__all__ = ['func_a1', 'func_a2', 'ClassA1', 'ClassA2', ...]

from a import func_a1, func_a2, ClassA1, ClassA2
from b import func_b1, func_b2, ClassB1, ClassB2
from c import func_c1, func_c2, ClassC1, ClassC2
from d import func_d1, func_d2, ClassD1, ClassD2

Ответ 4

Вот решение, вдохновленное однофункциональными модулями Javascript:

def __init__module():
    from os import path

    def _module_export_1():
        return path.abspath('../foo')

    def _module_export_2():
        return path.relpath('foo/bar', 'foo')

    g = globals()
    g['module_export_1'] = _module_export_1
    g['module_export_2'] = _module_export_2

__init__module()

Хотя модулю необходимо импортировать "путь" из os, "путь" не загрязняет пространство имен модулей. Единственным прорывом в пространстве имен модулей является __init_module(), который явно помечен частным префиксом с двойным подчеркиванием.

Другой вариант - импортировать необходимые модули в верхней части каждой функции, а не в верхнюю часть вашего модуля. После первого импорта модуля, последующий импорт - это просто поиск в словаре sys.modules.

Но я согласен с другими комментаторами здесь - соглашение Python не должно беспокоиться о загрязнении пространства имен модулей и просто сделать очевидным для пользователей вашего модуля, какие части пространства имен являются вашим общедоступным API и которые являются внутренними.