使用sys.settrace可以实现哪些有趣的黑科技?

27 投票
8 回答
12641 浏览
提问于 2025-04-15 15:46

我很喜欢用 settrace 来修改传给函数的参数,像这样:

import sys

def trace_func(frame,event,arg):
    value = frame.f_locals["a"]
    if value % 2 == 0:
        value += 1
        frame.f_locals["a"] = value

def f(a):
    print a

if __name__ == "__main__":
    sys.settrace(trace_func)
    for i in range(0,5):
        f(i)

这样会打印出:

1
1
3
3
5

使用 settrace 你还可以做些什么有趣的事情呢?

8 个回答

12

当然,代码覆盖率是通过跟踪功能来实现的。我们之前没有的一个很酷的功能是分支覆盖率测量,这个功能正在顺利开发中,快要在coverage.py的测试版中发布了。

举个例子,考虑这个函数:

def foo(x):
    if x:
        y = 10
    return y

如果你用这个调用来测试它:

assert foo(1) == 10

那么语句覆盖率会告诉你这个函数的所有行都被执行了。但是,这个函数里有一个简单的问题:用0来调用它会引发一个UnboundLocalError(未绑定的局部变量错误)。

分支测量会告诉你,代码中有一个分支没有被完全执行,因为这个分支的只有一条路径被走过。

26

我做了一个叫做 pycallgraph 的模块,它可以通过 sys.settrace() 来生成调用图。

30

我强烈建议不要滥用settrace这个工具。我假设你对这些内容有一定了解,但后面可能会有其他人不太明白。这里有几个原因:

  1. settrace是个很笨重的工具。虽然提问者的例子很简单,但几乎没有办法把它扩展到真正的系统中去使用。

  2. 它很神秘。任何人看到你的代码都会完全搞不懂它为什么会这样运行。

  3. 它很慢。每执行一行Python代码就调用一次Python函数,这会让你的程序变得非常慢。

  4. 通常是没必要的。这里的原始例子可以用其他几种方法实现(比如修改函数、用装饰器包裹函数、通过另一个函数调用等),这些方法都比settrace要好。

  5. 很难正确使用。在原始例子中,如果你不是直接调用f,而是调用g再调用f,你的跟踪函数就无法正常工作,因为你从跟踪函数返回了None,所以它只会被调用一次,然后就被遗忘了。

  6. 它会让其他工具失效。这个程序将无法调试(因为调试器使用settrace),也无法进行跟踪,无法测量代码覆盖率等等。这部分是因为Python的开发者没有考虑周全:他们给了我们settrace,但没有gettrace,所以很难让两个跟踪函数一起工作。

跟踪函数可以用来做一些很酷的黑科技。能够滥用它确实很有趣,但请不要把它用在实际项目中。如果我说得有点严厉,我很抱歉,但在真实代码中这样做会很麻烦。例如,DecoratorTools使用了一个跟踪函数,让这种语法在Python 2.3中可以工作:

# Method decorator example
from peak.util.decorators import decorate

class Demo1(object):
    decorate(classmethod)   # equivalent to @classmethod
    def example(cls):
        print "hello from", cls

这是个不错的黑科技,但不幸的是,任何使用DecoratorTools的代码都无法与coverage.py(或者调试器)一起工作。如果你问我,这样的权衡并不好。我修改了coverage.py,提供了一种模式让它可以与DecoratorTools一起工作,但我真希望我不需要这样做。

甚至标准库中的代码有时也会搞错这些事情。Pyexpat决定和其他扩展模块不同,以Python代码的方式调用跟踪函数。可惜他们做得很糟糕

</rant>

撰写回答