拦截模块调用?

2 投票
5 回答
1423 浏览
提问于 2025-04-16 08:15

我想要“拦截”对一个特定模块的所有调用,然后把这些调用转到另一个对象上。这样做是为了让我能有一个比较简单的插件架构。

比如,在main.py文件里

import renderer

renderer.draw('circle')

在renderer.py文件里

specificRenderer = OpenGLRenderer()

#Then, i'd like to route all calls from main.py so that
#specificRenderer.methodName(methodArgs) is called
# i.e. the above example would call specificRenderer.draw('circle')

这意味着任何函数都可以直接导入renderer并使用它,而不需要担心具体的细节。这也意味着我只需创建另一个对象,并把它赋值给renderer.py中的'specificRenderer',就可以完全更换渲染器。

有什么想法吗?

5 个回答

2

我的回答和@kindall的很相似,不过我这个想法是从别的地方得来的。我的方法更进一步,它用你自己设计的一个类的实例来替换通常放在sys.modules列表中的模块对象。至少,这个类需要看起来像这样:

文件 renderer.py

class _renderer(object):
    def __init__(self, specificRenderer):
        self.specificRenderer = specificRenderer
    def __getattr__(self, name):
        return getattr(self.specificRenderer, name)

if __name__ != '__main__':
    import sys
#    from some_module import OpenGLRenderer
    sys.modules[__name__] = _renderer(OpenGLRenderer())

__getattr__()这个方法的作用是把大部分属性的访问请求转发给真正的渲染器对象。这样做的好处是,你可以在私有的_renderer类中添加自己的属性,并通过renderer对象来访问它们,就好像它们是OpenGLRenderer对象的一部分一样。如果你给它们起的名字和OpenGLRenderer对象里已有的属性相同,那么这些属性会被优先调用。你可以选择转发、记录、忽略或者修改这个调用,然后再传递给真正的对象,这在某些情况下非常方便。

放在sys.modules中的类实例实际上是单例的,所以如果在应用程序的其他脚本中导入这个模块,它们都会共享第一个创建的那个实例。

2

最简单的方法就是让 main.py 里面做

from renderer import renderer

然后把 specificRenderer 改成 renderer 就可以了。

5

renderer.py 文件中:

import sys

if __name__ != "__main__":
    sys.modules[__name__] = OpenGLRenderer()

现在模块的名字和 OpenGLRenderer 实例关联起来了,其他模块中使用 import renderer 的时候,会得到同一个实例。

其实,你甚至不需要单独的模块。你可以直接在你的主模块里这样做:

import sys
sys.modules["renderer"] = OpenGLRenderer()
import renderer   # gives current module access to the "module"

... 这应该是你主模块中的第一件事。其他模块中导入 renderer 时,依然会指向同一个实例。

你确定你真的想这么做吗?这并不是人们通常期待模块的行为方式。

撰写回答