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