Пакетно-зависимые импортные хуки в 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 если он не может обработать элемент пути, и вернуть объект-импортер, если он может обработать элемент пути.

Другие вопросы по тегам