Пакетно-зависимые импортные хуки в Python
Я работаю над созданием модуля Python, который отображает API, предоставляемый другим языком / фреймворком, в Python. В идеале я хотел бы, чтобы это было представлено в виде единого корневого пакета, который предоставляет вспомогательные методы и который отображает все пространства имен в этой другой структуре в пакеты / модули Python. Для удобства, давайте возьмем CLR в качестве примера:
import clr.System.Data
import clr.System.Windows.Forms
Вот clr
это волшебный пакет верхнего уровня, который предоставляет пространства имен CLR System.Data
а также System.Windows.Forms
подпакеты / подмодули (насколько я вижу, пакет - это просто модуль с дочерними модулями / пакетами; все еще допустимо иметь в нем другие виды элементов).
Я прочитал PEP-302 и написал простую программу-прототип, которая достигает аналогичного эффекта путем установки meta_path
крюк. clr
сам модуль является правильным модулем Python, который при импорте устанавливает __path__ = []
(делая это пакет, так что import
даже пытается искать подмодули вообще), и регистрирует хук. Сам перехватчик перехватывает любую загрузку пакета, где полное имя пакета начинается с "clr."
, динамически создает новый модуль, используя imp.new_module()
, регистрирует это в sys.modules
и использует пиксельную пыль и радугу, чтобы заполнить ее классами и методами из исходного API. Вот код:
clr.py
import sys
import imp
class MyLoader:
def load_module(self, fullname):
try:
return sys.modules[fullname]
except KeyError:
pass
print("--- load ---")
print(fullname)
m = imp.new_module(fullname)
m.__file__ = "clr:" + fullname
m.__path__ = []
m.__loader__ = self
m.speak = lambda: print("I'm " + fullname)
sys.modules.setdefault(fullname, m)
return m
class MyFinder:
def find_module(self, fullname, path = None):
print("--- find ---")
print(fullname)
print(path)
if fullname.startswith("clr."):
return MyLoader()
return None
print("--- init ---")
__path__ = []
sys.meta_path.append(MyFinder())
test.py
import clr.Foo.Bar.Baz
clr.Foo.speak()
clr.Foo.Bar.speak()
clr.Foo.Bar.Baz.speak()
В целом это, кажется, работает нормально. Python гарантирует, что модули в цепочке импортируются слева направо, поэтому clr
всегда импортируется первым и устанавливает хук, который позволяет импортировать оставшуюся часть цепочки.
Тем не менее, мне интересно, если то, что я делаю здесь, излишне. В конце концов, я устанавливаю глобальный хук, который будет вызываться для любого импорта модуля, даже если я отфильтрую те, которые меня не интересуют. Возможно, есть какой-нибудь способ установить хук, который будет вызываться только для импорта из моего конкретного пакета, а не другие? Или вышеприведенный Правильный способ делать подобные вещи в Python?
1 ответ
В целом, я думаю, что ваш подход выглядит хорошо. Я не стал бы беспокоиться о том, что он "глобальный", поскольку весь смысл в том, чтобы указать, какие пути должны обрабатываться вами. Перемещение этого теста в логику импорта просто излишне усложнит его, поэтому решение остается за разработчиком хука.
Только одна небольшая проблема, может быть, вы могли бы использовать sys.path_hooks
? Похоже, что он немного менее "мощный", чем sys.meta_path
sys.path_hooks
это список вызываемых объектов, которые будут последовательно проверяться, чтобы определить, могут ли они обрабатывать данный элемент пути. Вызываемый объект вызывается с одним аргументом, элементом пути. Вызываемый должен поднятьImportError
если он не может обработать элемент пути, и вернуть объект-импортер, если он может обработать элемент пути.