aspect-oriented techniques in python?
So I have an 开发者_JAVA百科interesting problem in Python, that could possibly be solved with aspect-oriented techniques. Here's the situation:
- I have a bunch of modules, each of which has a bunch of functions.
- I have an executable that calls some set of functions in those modules.
- When the executable calls one of those functions, I want a log statement generated with the details of the function call (name & parameters)
- When one of the modules calls one of the other modules' functions, I do NOT want any log statement generated.
Is there any convenient way to do this in Python, without sticking logging statements inside every one of the modules' functions?
Modify the behavior of the functions in the executable can be done using a decorator:
#!/usr/bin/env python
from module1 import foo
from module2 import bar
def trace(f):
def tracewrapper(*arg, **kw):
arg_str=','.join(['%r'%a for a in arg]+['%s=%s'%(key,kw[key]) for key in kw])
print "%s(%s)" % (f.__name__, arg_str)
return f(*arg, **kw)
return tracewrapper
verbose_functions=[foo,bar] # add whatever functions you want logged here
for func in verbose_functions:
globals()[func.func_name]=trace(func)
Since you are only modifying the definition of the functions in the executable's namespace, the modules' functions remain untouched. When one module's function calls another module's function, it is undecorated by trace and no log statement is generated.
If you would like to log function calls only when they come directly from main(), then you could use a trace decorator like this:
import traceback
def trace(f,filename,funcname):
def tracewrapper(*arg, **kw):
stacks=traceback.extract_stack()
(s_filename,s_lineno,s_funcname,s_text)=stacks[-2]
# Alternatively, you can search the entire call stack
# for (s_filename,s_lineno,s_funcname,s_text) in stacks:
if s_filename.endswith(filename) and s_funcname==funcname:
arg_str=','.join(['%r'%a for a in arg]+
['%s=%s'%(key,kw[key]) for key in kw])
print "%s(%s)" % (f.__name__, arg_str)
return f(*arg, **kw)
return tracewrapper
verbose_functions=[foo,bar] # add whatever functions you want logged here
for func in verbose_functions:
# You can pass the module's filename and the function name here
globals()[func.func_name]=trace(func,'test.py','main')
Note that with the above trace
def baz():
foo(3,4)
def main():
foo(1,2,'Hi')
bar(x=3)
baz()
would log the foo(1,2,'Hi')
and bar(x=3)
calls, but not foo(3,4)
since this
call does not come directly from main. However, it does come indirectly from main, since main calls baz
. If you'd like to log the foo(3,4)
call, then you'd want to
loop through the entire stack:
import traceback
def trace(f,filename,funcname):
def tracewrapper(*arg, **kw):
stacks=traceback.extract_stack()
for (s_filename,s_lineno,s_funcname,s_text) in stacks:
if s_filename.endswith(filename) and s_funcname==funcname:
arg_str=','.join(['%r'%a for a in arg]+
['%s=%s'%(key,kw[key]) for key in kw])
print "%s(%s)" % (f.__name__, arg_str)
return f(*arg, **kw)
return tracewrapper
The simplest and first solution that comes to mind, is using a proxy module.
#fooproxy.py
import foo
import logger #not implemented here - use your imagination :)
def bar(baz):
logger.method("foo.bar")
return foo.bar(baz)
#foo.py
def bar(baz):
print "The real McCoy"
#main.py
import fooproxy as foo
foo.bar()
I don't know the right way to do this in a general case, but what comes to mind is just hacking the executable a little. Something like:
class DebugCallWrapper(object):
def __init__(self, callable):
self.callable = callable
def __call__(*args,**kwargs):
log.debug(str(callable) + str(args) + str(kwargs))
callable(*args,**kwargs)
class Top(object):
def __getattribute__(self, name):
real_object = globals()[name]
if callable(real_object):
return DebugCallWrapper(real_object)
else:
return real_object
top = Top()
import foo
#instead of foo.bar()
top.foo.bar()
This kind of thing could get you into a lot of trouble, and the above probably won't be usable without some tweaking. But maybe it's an idea.
精彩评论