Подтвердить что ты не робот

Как выполнять импорт в модуле python без загрязнения его пространства имен?

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

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

  • Импортируйте классы на уровне модуля с помощью 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? Есть ли еще одно хорошее решение, которое я пропустил?

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

Когда вы задаете себе такие вопросы, спросите себя, что делает код более понятным, а не то, что делает код более удобным для написания. Вы пишете его один раз, но вы его много читаете.

Ответ 3

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

Эффект заключается в том, что другой код, следуя обычному соглашению на 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

Ответ 4

В этой ситуации я бы пошел с файлом 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']

Теперь любые функции/классы/переменные/etc, которые находятся в вашем модуле, но не в ваших модулях __all__ не будут отображаться в help() и не будут импортироваться from mymodule import * Подробнее см. для более структурированного импорта python.

Ответ 5

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

import foreignmodule as fm

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

Ответ 6

Я знаю, что это старый вопрос. Возможно, это не "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

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