Запретить пакеты Python при повторном экспортировании импортированных имен
В пакете Python у меня есть файловая структура
package/
__init__.py
import_me.py
Предполагается, что файл import_me.py
предоставляет фрагменты функций:
import re
import sys
def hello():
pass
чтобы package.import_me.hello
можно было импортировать динамически через import
. К сожалению, это также позволяет импортировать re
и sys
как package.import_me.re
и package.import_me.sys
соответственно.
Есть ли способ предотвратить повторный экспорт импортированных модулей в import_me.py
? Предпочтительно это должно выходить за рамки искажения имени или префикса подчеркивания импортированных модулей, поскольку в моем случае это может создать проблему безопасности в некоторых случаях.
Ответы
Ответ 1
Нет простого способа запретить импорт глобального имени из модуля; Python просто не построен таким образом.
В то время как вы могли бы достичь запрещающей цели, если бы вы написали свою собственную функцию __import__
и затеняли встроенную, но я сомневаюсь, что затраты времени и тестирования будут стоить того и не будут полностью эффективными.
Что вы можете сделать, это импортировать зависимые модули с лидирующим знаком подчеркивания, который является стандартной идиомой Python для передачи "подробностей реализации, использования на свой страх и риск":
import re as _re
import sys as _sys
def hello():
pass
Заметка
Хотя просто удаление импортированных модулей как способ не позволить им импортироваться, похоже, что это может сработать, на самом деле это не так:
import re
import sys
def hello():
sys
print('hello')
del re
del sys
а затем импортировать и использовать hello
:
>>> import del_mod
>>> del_mod.hello()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "del_mod.py", line 5, in hello
sys
NameError: global name 'sys' is not defined
Ответ 2
Нет простого способа запретить импорт глобального имени из модуля; но на самом деле вам не нужно. Python позволяет использовать локальный импорт вместо глобального:
def foo():
import sys
print(sys.copyright)
sys.copyright # Throws NameError
Уютный и простой.
На самом деле, я считаю, что использование локального импорта должно быть хорошей практикой, а глобальные - лишь данью C или наследию.
UPD: Очевидным недостатком является то, что import sys
statement будет выполняться каждый раз, когда вызывается эта функция, что может быть недоступным. Но вместо этого вы можете создать вызываемый объект:
class Callable(object):
import sys as _sys
def __call__(self):
print(self._sys.copyright)
foo = Callable()
foo()
Хотя мне лично не нравится этот подход, он может работать лучше с родовыми классами.
Ответ 3
Там пара вариантов:
-
Поместите None
в sys.modules
для модуля:
>>> import sys
>>> import re
>>> del re
>>> sys.modules['re'] = None
>>> import re
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named re
-
Используйте пакет RestrictedPython или пакет pysandbox.
Обязательно ознакомьтесь с этой статьей также на песочнице Python.
Ответ 4
1. Функция инициализации
Альтернативой может быть обертывание определений в функцию инициализации.
## --- exporttest.py ---
def _init():
import os # effectively hidden
global get_ext # effectively exports it
def get_ext(filename):
return _pointless_subfunc(filename)
# underscore optional, but good
def _pointless_subfunc(filename): # for the sake of documentation
return os.path.splitext(filename)[1]
if __name__ == '__main__': # for interactive debugging (or doctest)
globals().update(locals()) # we want all definitions accessible
import doctest
doctest.testmod()
_init()
print('is ''get_ext'' accessible? ', 'get_ext' in globals())
print('is ''_pointless_subfunc'' accessible?', '_pointless_subfunc' in globals())
print('is ''os'' accessible? ', 'os' in globals())
Для сравнения:
>>> python3 -m exporttest
is ''get_ext'' accessible? True
is ''_pointless_subfunc'' accessible? True
is ''os'' accessible? True
>>> python3 -c "import exporttest"
is ''get_ext'' accessible? True
is ''_pointless_subfunc'' accessible? False
is ''os'' accessible? False
1.1. преимущества
- Фактическое скрытие импорта.
- Более удобный для интерактивного поиска кода, поскольку
dir(exporttest)
беспорядок.
1.2. Недостатки
-
К сожалению, в отличие от import MODULE as _MODULE
шаблона import MODULE as _MODULE
, он не играет хорошо с pylint.
C: 4, 4: Invalid constant name "get_ext" (invalid-name)
W: 4, 4: Using global for 'get_ext' but no assignment is done (global-variable-not-assigned)
W: 5, 4: Unused variable 'get_ext' (unused-variable)
2. Объятия __all__
После дальнейшего чтения я обнаружил, что питонический способ сделать это - полагаться на __all__
. Он контролирует не только то, что экспортируется from MODULE import *
, но и то, что появляется в help(MODULE)
, и в соответствии с мантрой "Мы все взрослые здесь" это собственная ошибка пользователей, если он использует все, что не задокументировано как общественности.
2.1. преимущества
Инструмент имеет лучшую поддержку для этого подхода (например, посредством поддержки редактора для автомиграций через библиотеку importmagic).
2.2. Недостатки
Лично я нахожу, что целая "мы все взрослые" мантра весьма наивна, но это питонический путь.
Ответ 5
EDIT: Это не работает в большинстве случаев. См. Другие ответы.
Я знаю, что этот вопрос старый, но если вы просто
import re
import sys
def hello():
pass
del re
del sys
то вы не сможете импортировать re
или sys
из import_me.py
, и таким образом вам не нужно перемещать импорт из начала файла.