Я работаю над созданием модуля 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
hook. Сам модуль clr
является правильным модулем Python, который при импорте устанавливает __path__ = []
(делает его пакетом, так что import
даже пытается искать подмодули вообще) и регистрирует hook. Крюк сам перехватывает любую загрузку пакета, где полное имя пакета начинается с "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?