16

我正在创建一个 Python 模块,将不同语言/框架提供的 API 映射到 Python 中。理想情况下,我希望将其呈现为单个根包,该包公开辅助方法,并将该其他框架中的所有命名空间映射到 Python 包/模块。为了方便,我们以CLR为例:

import clr.System.Data
import clr.System.Windows.Forms

clr是一个神奇的顶级包,它公开了 CLR 命名空间System.DataSystem.Windows.Forms子包/子模块(据我所知,一个包只是一个带有子模块/包的模块;其中包含其他类型的成员仍然有效)。

我已经阅读了PEP-302并编写了一个简单的原型程序,通过安装自定义meta_path钩子实现了类似的效果。模块本身是一个合适的clrPython 模块,在导入时会设置__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中做这种事情的正确方法吗?

4

1 回答 1

5

总的来说,我认为您的方法看起来不错。我不担心它是“全局的”,因为重点是指定您应该处理哪些路径。在导入逻辑中移动这个测试只会不必要地复杂化它,所以它留给钩子的实现者来决定。

只是一个小问题,也许你可以使用sys.path_hooks?它似乎没有那么“强大”sys.meta_path

sys.path_hooks是可调用的列表,将按顺序检查以确定它们是否可以处理给定的路径项。使用一个参数调用可调用对象,即路径项。如果无法处理路径项,则可调用必须引发ImportError,如果可以处理路径项,则返回导入器对象。

于 2011-09-01T10:14:26.297 回答