Как сохранить имена подмодулей вне пространства имен пакета 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. Эти имена являются деталями реализации, а не частью моего интерфейса. Я не хочу, чтобы они отображались как «общедоступные» символы. Каков наилучший способ избавиться от них?

Варианты, которые я рассматривал,

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

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

    del a, b, c, d
    

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

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

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

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


person Sven Marnach    schedule 10.03.2011    source источник
comment
С таким же успехом можно пойти за газировкой. То есть никого, кроме вас, не волнует, что находится в пространстве имен пакета.   -  person Ignacio Vazquez-Abrams    schedule 10.03.2011
comment
@Ignacio: Возможно, вы правы :) Меня раздражает, например, при интерактивном использовании, когда нежелательные имена мешают раскрытию вкладки.   -  person Sven Marnach    schedule 10.03.2011
comment
Это только мое мнение, но import __init__ кажется более хакерским в варианте 2, учитывая все обстоятельства.   -  person JAB    schedule 07.06.2012


Ответы (4)


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

person Ignacio Vazquez-Abrams    schedule 10.03.2011

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

Например, если вы посмотрите в 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 стали деталями реализации в этом файле.

person Ethan Furman    schedule 10.11.2011

Вот решение, вдохновленное однофункциональными модулями 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()

Хотя модуль должен импортировать «путь» из ОС, «путь» не загрязняет пространство имен модуля. Единственным недостатком в пространстве имен модулей является __init_module(), который четко помечен как private с помощью префикса с двойным подчеркиванием.

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

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

person A. Jesse Jiryu Davis    schedule 04.04.2011
comment
К сожалению, этот подход не работает. Как вы можете видеть в моем посте, я никогда не импортирую имя a в пространство имен пакета — я просто использую from a import func_a1,.... Если бы я сделал это в обычном модуле, в пространстве имен модуля появились бы только имена func_a1, ... Но поскольку a является подмодулем пакета, его имя a также вставляется в пространство имен пакета. То же самое произошло бы, если бы я импортировал все из функции. - person Sven Marnach; 04.04.2011

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

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

В 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
person dcrosta    schedule 10.03.2011
comment
Но это не влияет на содержимое пространства имен при прямом доступе к нему. - person Ignacio Vazquez-Abrams; 10.03.2011
comment
Гах, я неправильно понял ваш вопрос... это работает, если вы делаете from mypackage import *, как я и предполагал. Кажется, в этом случае это не работает, даже если вы определяете __all__ в своих модулях a, b и т. д. - person dcrosta; 10.03.2011