Как мне выполнить импорт в модуле python, не загрязняя его пространство имен? - PullRequest
29 голосов
/ 15 сентября 2011

Я разрабатываю пакет Python для работы с некоторыми научными данными. Существует множество часто используемых классов и функций из других модулей и пакетов, включая numpy, которые мне нужны практически для каждой функции, определенной в любом модуле пакета.

Каким был бы Pythonic способ иметь с ними дело? Я рассмотрел несколько вариантов, но у каждого есть свои недостатки.

  • Импорт классов на уровне модуля с помощью from foreignmodule import Class1, Class2, function1, function2
    Тогда импортированные функции и классы легко доступны из любой функции. С другой стороны, они загрязняют пространство имен модуля, делая dir(package.module) и help(package.module) загроможденными импортированными функциями

  • Импорт классов на уровне функций с помощью from foreignmodule import Class1, Class2, function1, function2
    Функции и классы легко доступны и не загрязняют модуль, но импорт из до дюжины модулей в каждой функции выглядит как много дублирующегося кода.

  • Импорт модулей на уровне модулей с помощью import foreignmodule
    Не слишком сильное загрязнение компенсируется необходимостью добавлять имя модуля к каждой функции или вызову класса.

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

    def _export():
        from foreignmodule import Class1, Class2, function1, function2
        def myfunc(x):
            return function1(x, function2(x))
        return myfunc
    myfunc = _export()
    del _export
    

    Это позволяет решить обе проблемы: загрязнение пространства имен модулей и простоту использования для функций ... но, похоже, это вовсе не Pythonic.

Так, какое решение является самым Pythonic? Есть ли еще одно хорошее решение, которое я упустил?

Ответы [ 6 ]

19 голосов
/ 15 сентября 2011

Продолжайте и делайте как обычно from W import X, Y, Z, а затем используйте специальный символ __all__, чтобы определить, какие именно символы вы собираетесь импортировать из вашего модуля:

__all__ = ('MyClass1', 'MyClass2', 'myvar1', …)

Определяет символы, которые будут импортированы в модуль пользователя, если они import * из вашего модуля.

Как правило, программисты Python не должны использовать dir(), чтобы выяснить, как использовать ваш модуль, и если они это делают, это может указывать на проблему где-то еще. Они должны читать вашу документацию или вводить help(yourmodule), чтобы выяснить, как использовать вашу библиотеку. Или же они могут просматривать исходный код самостоятельно, и в этом случае (а) разница между импортируемыми вами и определяемыми вами вещами совершенно ясна, и (б) они увидят объявление __all__ и узнают, с какими игрушками они должны играть .

Если вы попытаетесь поддержать dir() в такой ситуации для задачи, для которой она не предназначена, вам придется навязать раздражающие ограничения для вашего собственного кода, как я надеюсь, ясно из других ответов здесь. Мой совет: не делай этого! Посмотрите на Стандартную библиотеку для руководства: она делает from … import … всякий раз, когда этого требуют ясность и краткость кода, и предоставляет (1) информативные строки документации, (2) полную документацию и (3) читаемый код, чтобы никто никогда не имел запустить dir() на модуле и попытаться отличить импорт от того, что фактически определено в модуле.

10 голосов
/ 07 февраля 2014

Один из методов, которые я видел, в том числе в стандартной библиотеке, заключается в использовании import <em>module</em> as <em>_module</em> или from <em>module</em> import <em>var</em> as <em>_var</em>, т. Е. Присвоении импортированных модулей / переменных именам, начинающимся с подчеркивания.

В результате другой код, следуя обычному соглашению Python, обрабатывает эти элементы как частные. Это относится даже к коду, который не смотрит на __all__, например, к функции автозаполнения IPython.

Пример из модуля Python 3.3 random:

from warnings import warn as _warn
from types import MethodType as _MethodType, BuiltinMethodType as _BuiltinMethodType
from math import log as _log, exp as _exp, pi as _pi, e as _e, ceil as _ceil
from math import sqrt as _sqrt, acos as _acos, cos as _cos, sin as _sin
from os import urandom as _urandom
from collections.abc import Set as _Set, Sequence as _Sequence
from hashlib import sha512 as _sha512

Другой способ - выполнить импорт в области действия функции, чтобы они стали локальными переменными:

"""Some module"""
# imports conventionally go here
def some_function(arg):
    "Do something with arg."
    import re  # Regular expressions solve everything
    ...

Основное обоснование для этого заключается в том, что он фактически ленив, задерживая импорт зависимостей модуля до тех пор, пока они не будут фактически использованы. Предположим, одна функция в модуле зависит от конкретной огромной библиотеки. Импорт библиотеки в верхней части файла будет означать, что импорт модуля загрузит всю библиотеку. Таким образом, импорт модуля может быть быстрым, и только клиентский код, который фактически вызывает эту функцию, несет расходы по загрузке библиотеки. Кроме того, если библиотека зависимостей недоступна, клиентский код, который не нуждается в зависимой функции, может импортировать модуль и вызывать другие функции. Недостатком является то, что использование импорта на уровне функций скрывает зависимости вашего кода.

Пример из Python 3.3 os.py:

def get_exec_path(env=None):
    """[...]"""
    # Use a local import instead of a global import to limit the number of
    # modules loaded at startup: the os module is always loaded at startup by
    # Python. It may also avoid a bootstrap issue.
    import warnings
9 голосов
/ 15 сентября 2011

Импортировать модуль целиком: import foreignmodule.То, что вы считаете недостатком, на самом деле является преимуществом.А именно, добавление имени модуля облегчает поддержку вашего кода и делает его более самодокументируемым.

Через шесть месяцев, когда вы посмотрите на строку кода, такую ​​как foo = Bar(baz), вы можете спросить себя, какой модуль Bar пришел, но с foo = cleverlib.Bar это гораздо меньше загадка.

Конечно, чем меньше у вас импорта, тем меньше проблем.Для небольших программ с небольшим количеством зависимостей это действительно не имеет большого значения.

Когда вы задаете себе подобные вопросы, задайте себе вопрос, что облегчает понимание кода, а не то, что его легче писать.,Ты пишешь это один раз, но много читаешь.

3 голосов
/ 15 сентября 2011

В этой ситуации я бы пошел с all_imports.py файлом, в котором были все

from foreignmodule import .....
from another module import .....

, а затем с вашими рабочими модулями

import all_imports as fgn # or whatever you want to prepend
...
something = fgn.Class1()

Еще одна вещь, о которой нужно знать

__all__ = ['func1', 'func2', 'this', 'that']

Теперь все функции / классы / переменные / и т. Д., Которые есть в вашем модуле, но не в __all__ ваших модулей, не будут отображаться в help() и будут выиграны 'from mymodule import * может быть импортировано * См. Как сделать импорт Python более структурированным? для получения дополнительной информации.

1 голос
/ 15 сентября 2011

Я бы пошел на компромисс и просто выбрал бы короткий псевдоним для внешнего модуля:

import foreignmodule as fm

Это полностью спасает вас от загрязнения (возможно, более серьезной проблемы) и по крайней мере уменьшаетпредваряющее бремя.

0 голосов
/ 01 мая 2019

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

def module():
    global MyPublicClass,ExportedModule

    import somemodule as ExportedModule
    import anothermodule as PrivateModule

    class MyPublicClass:
        def __init__(self):
            pass

    class MyPrivateClass:
        def __init__(self):
            pass

module()
del module

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

def module():
    global A

    i,j,k = 1,2,3

    class A:
        pass

module()
del module

def module():
    global B

    i,j,k = 7,8,9 # doesn't overwrite previous declarations

    class B:
        pass

module()
del module

Однако имейте в виду, что их публичные определения, конечно, будут совпадать.

...