Intercepting module calls?
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.
For example, in main.py
import renderer
renderer.draw('circle')
In 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')
This means that any function can just import renderer and use it, without worrying about the details. It also means that I can co开发者_如何学编程mpletely change the renderer just by creating another object and assigning it to the 'specificRenderer' value in renderer.py
Any ideas?
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.
The simplest way to do that is to have main.py do
from renderer import renderer
instead, then just name specificRenderer
renderer
.
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.
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.
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.
精彩评论