Intercepting module calls?

后端 未结 5 1140
粉色の甜心
粉色の甜心 2021-01-15 13:20

I\'m trying to \'intercept\' all calls to a specific module, and reroute them to another object. I\'d like to do this so that I can have a fairly simple plugin architecture.

相关标签:
5条回答
  • 2021-01-15 13:20

    If you don't mind that import renderer results an object rather than a module, then see kindall's brilliant solution.

    If you want to make @property work (i.e. each time you fetch renderer.mytime, you want the function corresponding to OpenGLRenderer.mytime get called) and you want to keep renderer as a module, then it's impossible. Example:

    import time
    class OpenGLRenderer(object):
      @property
      def mytime(self):
        return time.time()
    

    If you don't care about properties, i.e. it's OK for you that mytime gets called only once (at module load time), and it will keep returning the same timestamp, then it's possible to do it by copying all symbols from the object to the module:

    # renderer.py
    specificRenderer = OpenGLRenderer()
    for name in dir(specificRenderer):
      globals()[name] = getattr(specificRenderer, name)
    

    However, this is a one-time copy. If you add methods or other attributes to specificRenderer later dynamically, or change some attributes later, then they won't be automatically copied to the renderer module. This can be fixed, however, by some ugly __setattr__ hacking.

    0 讨论(0)
  • 2021-01-15 13:21

    Edit: This answer does not do what the OP wants; it doesn't instantiate an object and then let calls to a module be redirected to that same object. This answer is about changing which rendering module is being used.

    Easiest might be to import the OpenGLRenderer in the main.py program like this:

    import OpenGLRenderer as renderer
    

    That's code in just one place, and in the rest of your module OpenGLRenderer can be referred to as renderer.

    If you have several modules like main.py, you could have your renderer.py file be just the same line:

    import OpenGLRenderer as renderer
    

    and then other modules can use

    from renderer import renderer
    

    If OpenGLRenderer doesn't quite quack right yet, you can monkeypatch it to work as you need in the renderer.py module.

    0 讨论(0)
  • 2021-01-15 13:23

    In renderer.py:

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

    The module name is now mapped to the OpenGLRenderer instance, and import renderer in other modules will get the same instance.

    Actually, you don't even need the separate module. You can just do:

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

    ... first thing in your main module. Imports of renderer in other modules, once again, will refer to the same instance.

    Are you sure you really want to do this in the first place? It isn't really how people expect modules to behave.

    0 讨论(0)
  • 2021-01-15 13:30

    My answer is very similar to @kindall's although I got the idea elsewhere. It goes a step further in the sense that it replaces the module object that's usually put in the sys.modules list with an instance of a class of your own design. At a minimum such a class would need to look something like this:

    File 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())
    

    The __getattr__() method simply forwards most attribute accesses on to the real renderer object. The advantage to this level of indirection is that with it you can add your own attributes to the private _renderer class and access them through the renderer object imported just as though they were part of an OpenGLRenderer object. If you give them the same name as something already in an OpenGLRenderer object, they will be called instead, are free to forward, log, ignore, and/or modify the call before passing it along -- which can sometimes be very handy.

    Class instances placed in sys.modules are effectively singletons, so if the module is imported in other scripts in the application, they will all share the single instance created by the first one.

    0 讨论(0)
  • 2021-01-15 13:33

    The simplest way to do that is to have main.py do

    from renderer import renderer
    

    instead, then just name specificRenderer renderer.

    0 讨论(0)
提交回复
热议问题