我正在创建一个 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())
测试.py
import clr.Foo.Bar.Baz
clr.Foo.speak()
clr.Foo.Bar.speak()
clr.Foo.Bar.Baz.speak()
总而言之,这似乎工作正常。Python 保证链中的模块是从左到右导入的,因此clr
总是首先导入,并且它设置了允许导入链的其余部分的钩子。
但是,我想知道我在这里所做的是否是矫枉过正。毕竟,我正在安装一个全局钩子,任何模块导入都会调用它,即使我过滤掉了我不关心的那些。是否有某种方法可以安装一个只会从我的特定包导入而不是其他包的钩子?或者上面是在Python中做这种事情的正确方法吗?