Python:在代码块每行添加代码例程

8 投票
3 回答
2255 浏览
提问于 2025-04-18 11:51

我想让一段代码在另一段代码的每一行之后运行。例如,我希望在执行函数的下一行之前或之后评估一个全局变量。

比如,下面我试着在每一行的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 个回答

0

最好的答案可能取决于你真正想要做的事情,不过为了满足你的问题,我会这样做:

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")))))
0

听起来你需要一个调试工具,可以看看内置的 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 的简写,表示向前一步。

14

也许你在寻找 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_localsframe.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()

撰写回答