Python:在代码块每行添加代码例程
我想让一段代码在另一段代码的每一行之后运行。例如,我希望在执行函数的下一行之前或之后评估一个全局变量。
比如,下面我试着在每一行的foo()
函数之前打印“hello”。我觉得使用装饰器可以帮我,但它需要一些自省的功能,以便在我的foo()
函数的每一行前后添加我想要的内容。
我想做的事情大概是这样的:
>>> def foo():
... print 'bar'
... print 'barbar'
... print 'barbarbar'
>>> foo()
hello
bar
hello
barbar
hello
barbarbar
我该怎么做呢?__code__
对象能帮上忙吗?我需要同时使用装饰器和自省吗?
编辑:这是这个讨论的另一个例子:
>>> def foo():
... for i in range(0,3):
... print 'bar'
>>> foo()
hello
bar
hello
bar
hello
bar
在这个新例子中,我想在打印每个“bar”之前打印一个“hello”。
我这样做的主要目的是能够在执行下一行代码之前,执行另一个函数或测试任何类型的全局变量。想象一下,如果一个全局变量是True
,那么代码就会继续执行下一行;而如果全局变量是False
,那么就会停止执行这个函数。
编辑:从某种意义上说,我在寻找一个工具,可以在另一段代码中注入代码。
编辑:感谢unutbu,我已经实现了这个代码:
import sys
import time
import threading
class SetTrace(object):
"""
with SetTrace(monitor):
"""
def __init__(self, func):
self.func = func
def __enter__(self):
sys.settrace(self.func)
return self
def __exit__(self, ext_type, exc_value, traceback):
sys.settrace(None)
# http://effbot.org/zone/python-with-statement.htm
# When __exit__ returns True, the exception is swallowed.
# When __exit__ returns False, the exception is reraised.
# This catches Sentinel, and lets other errors through
# return isinstance(exc_value, Exception)
def monitor(frame, event, arg):
if event == "line":
if not running:
raise Exception("global running is False, exiting")
return monitor
def isRunning(function):
def defaultBehavior(*args):
with SetTrace(monitor):
ret = function(*args)
return ret
return defaultBehavior
@isRunning
def foo():
while True:
time.sleep(1)
print 'bar'
global running
running = True
thread = threading.Thread(target = foo)
thread.start()
time.sleep(3)
running = False
3 个回答
最好的答案可能取决于你真正想要做的事情,不过为了满足你的问题,我会这样做:
from itertools import chain
def print_(s):
print s
def print_f(s):
return (lambda: print_(s))
def execute_several(functions):
for f in functions:
f()
def prepend_printhello(function):
return (print_f("hello"), function)
def foo():
execute_several(chain(*map(prepend_printhello, map(print_f, ("bar", "barbar", "barbarbar")))))
听起来你需要一个调试工具,可以看看内置的 pdb
。使用pdb你可以这样做:
>>> def foo():
... import pdb;pdb.set_trace()
... print 'bar'
... print 'barbar'
... print 'barbarbar'
...
>>>
>>> foo()
> <stdin>(3)foo()
(Pdb) print 'hi'
hi
(Pdb) n
bar
> <stdin>(4)foo()
(Pdb) n
barbar
> <stdin>(5)foo()
(Pdb) n
barbarbar
--Return--
> <stdin>(5)foo()->None
(Pdb) n
--Return--
> <stdin>(1)<module>()->None
(Pdb) n
>>>
像大多数调试工具一样,它允许你逐行查看代码。你可以查阅文档获取更多信息,但在上面的例子中, pdb.set_trace()
这个调用会设置调试的入口点,并打开pdb控制台。在控制台中,你可以修改变量,做各种操作。n
只是 next
的简写,表示向前一步。
也许你在寻找 sys.settrace:
import sys
class SetTrace(object):
def __init__(self, func):
self.func = func
def __enter__(self):
sys.settrace(self.func)
return self
def __exit__(self, ext_type, exc_value, traceback):
sys.settrace(None)
def monitor(frame, event, arg):
if event == "line":
print('hello')
# print(frame.f_globals)
# print(frame.f_locals)
return monitor
def foo():
print 'bar'
print 'barbar'
print 'barbarbar'
with SetTrace(monitor):
foo()
会产生
hello
bar
hello
barbar
hello
barbarbar
hello
在 monitor
里,你可以通过 frame.f_locals
和 frame.f_globals
来访问 foo
的局部变量和全局变量。
可以查看这篇 文章,了解如何使用 sys.settrace
来调试代码的例子。
如何在 monitor
中停止 foo
:
最优雅的方式是在 foo
里面放一个条件语句,这样 foo
就可以检查什么时候退出。然后你可以在 monitor
中改变这个条件的值,从而控制 foo
什么时候退出。
但是,如果你不想或者不能修改 foo
,那么另一种选择是在 monitor
中抛出一个异常。这个异常会沿着调用栈向上冒泡,直到被捕获。如果你在 SetTrace.__exit__
中捕获到它,那么控制流程就会继续,就好像 foo
刚刚退出了一样。
import sys
class Sentinel(Exception): pass
class SetTrace(object):
"""
with SetTrace(monitor):
...
"""
def __init__(self, func):
self.func = func
def __enter__(self):
sys.settrace(self.func)
return self
def __exit__(self, ext_type, exc_value, traceback):
sys.settrace(None)
# http://effbot.org/zone/python-with-statement.htm
# When __exit__ returns True, the exception is swallowed.
# When __exit__ returns False, the exception is reraised.
# This catches Sentinel, and lets other errors through
return isinstance(exc_value, Sentinel)
def monitor(frame, event, arg):
if event == "line":
l = frame.f_locals
x = l.get('x', 0)
print('x = {}'.format(x))
if x > 3:
raise Sentinel()
return monitor
def foo():
x = 0
while True:
print 'bar'
x += 1
with SetTrace(monitor):
foo()