Как правильно использовать функцию Python __import__()
?
Существует два вида использования:
- прямой импорт
- крючок для изменения поведения при импорте
В большинстве случаев вам и не нужно этого делать.
Для импорта из пользовательского пространства
Лучшая практика - использовать importlib
. Но если вы настаиваете:
Тривиальное использование:
>>> sys = __import__('sys')
>>> sys
<module 'sys' (built-in)>
Complicated:
>>> os = __import__('os.path')
>>> os
<module 'os' from '/home/myuser/anaconda3/lib/python3.6/os.py'>
>>> os.path
<module 'posixpath' from '/home/myuser/anaconda3/lib/python3.6/posixpath.py'>
Если вам нужен самый правый дочерний модуль в имени, передайте непустой список, например, [None]
, до fromlist
:
>>> path = __import__('os.path', fromlist=[None])
>>> path
<module 'posixpath' from '/home/myuser/anaconda3/lib/python3.6/posixpath.py'>
Или, как заявляет документация, используйте importlib.import_module
:
>>> importlib = __import__('importlib')
>>> futures = importlib.import_module('concurrent.futures')
>>> futures
<module 'concurrent.futures' from '/home/myuser/anaconda3/lib/python3.6/concurrent/futures/__init__.py'>
Документация
Документы для __import__
являются наиболее запутанными из встроенных функций.
__import__(...)
__import__(name, globals=None, locals=None, fromlist=(), level=0) -> module
Import a module. Because this function is meant for use by the Python
interpreter and not for general use it is better to use
importlib.import_module() to programmatically import a module.
The globals argument is only used to determine the context;
they are not modified. The locals argument is unused. The fromlist
should be a list of names to emulate ``from name import ...'', or an
empty list to emulate ``import name''.
When importing a module from a package, note that __import__('A.B', ...)
returns package A when fromlist is empty, but its submodule B when
fromlist is not empty. Level is used to determine whether to perform
absolute or relative imports. 0 is absolute while a positive number
is the number of parent directories to search relative to the current module.
Если вы внимательно его прочитаете, вы почувствуете, что API изначально предназначался для обеспечения отложенной загрузки функций из модулей. Тем не менее, это не то, как работает CPython, и я не знаю, сумели ли это сделать другие реализации Python.
Вместо этого CPython выполняет весь код в пространстве имен модуля при первом импорте, после чего модуль кэшируется в sys.modules
.
__import__
все еще может быть полезным. Но понять, что он делает на основании документации, довольно сложно.
Полное использование __import__
Чтобы адаптировать полную функциональность для демонстрации текущего __import__
API, вот функция-оболочка с более чистым, лучше документированным API.
def importer(name, root_package=False, relative_globals=None, level=0):
""" We only import modules, functions can be looked up on the module.
Usage:
from foo.bar import baz
>>> baz = importer('foo.bar.baz')
import foo.bar.baz
>>> foo = importer('foo.bar.baz', root_package=True)
>>> foo.bar.baz
from .. import baz (level = number of dots)
>>> baz = importer('baz', relative_globals=globals(), level=2)
"""
return __import__(name, locals=None, # locals has no use
globals=relative_globals,
fromlist=[] if root_package else [None],
level=level)
Для демонстрации, например из сестринского пакета на базу:
baz = importer('foo.bar.baz')
foo = importer('foo.bar.baz', root_package=True)
baz2 = importer('bar.baz', relative_globals=globals(), level=2)
assert foo.bar.baz is baz is baz2
Динамический доступ к именам в модуле
Для динамического доступа к глобальным именам по имени из модуля baz используйте getattr
. Например:
for name in dir(baz):
print(getattr(baz, name))
Крюк для изменения поведения при импорте
Вы можете использовать __import__
, чтобы изменить или перехватить режим импорта. В этом случае давайте просто напечатаем аргументы, которые он получает, чтобы продемонстрировать, что мы его перехватываем:
old_import = __import__
def noisy_importer(name, locals, globals, fromlist, level):
print(f'name: {name!r}')
print(f'fromlist: {fromlist}')
print(f'level: {level}')
return old_import(name, locals, globals, fromlist, level)
import builtins
builtins.__import__ = noisy_importer
И теперь, когда вы импортируете, вы можете видеть эти важные аргументы.
>>> from os.path import join as opj
name: 'os.path'
fromlist: ('join',)
level: 0
>>> opj
<function join at 0x7fd08d882618>
Возможно, в этом контексте получение глобальных или локальных данных могло бы быть полезным, но об этом сразу не вспоминается.