Python中的面向切面技术?
我在Python中遇到了一个有趣的问题,可能可以用面向切面编程的技巧来解决。情况是这样的:
- 我有很多模块,每个模块里面都有很多函数。
- 我有一个可执行文件,它会调用这些模块中的一些函数。
- 当这个可执行文件调用某个函数时,我希望能生成一条日志,记录下这个函数的调用细节(比如函数名和参数)。
- 但是,当某个模块调用其他模块的函数时,我不想生成任何日志。
有没有什么方便的方法可以在Python中做到这一点,而不需要在每个模块的函数里都加上日志语句呢?
3 个回答
0
最简单、最先想到的解决办法,就是使用一个代理模块。
#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()
0
我不知道在一般情况下正确的做法是什么,但我想到的就是稍微修改一下可执行文件。大概是这样的:
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()
这种做法可能会让你陷入很多麻烦,而且上面的代码可能需要一些调整才能用。不过,也许这是个思路。
5
可以通过使用装饰器来改变可执行文件中函数的行为:
#!/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)
因为你只是修改了可执行文件命名空间中函数的定义,所以模块中的函数不会受到影响。当一个模块的函数调用另一个模块的函数时,这个调用不会被装饰,也不会生成日志记录。
如果你只想在函数调用直接来自main()时记录日志,可以使用像这样的追踪装饰器:
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')
注意,上面的追踪装饰器
def baz():
foo(3,4)
def main():
foo(1,2,'Hi')
bar(x=3)
baz()
会记录 foo(1,2,'Hi')
和 bar(x=3)
的调用,但不会记录 foo(3,4)
,因为这个调用并不是直接来自main。不过,它确实是间接来自main,因为main调用了 baz
。如果你想记录 foo(3,4)
的调用,那么你需要遍历整个调用栈:
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