16

有一个大型 python 项目,其中一个类的一个属性在某个地方有错误的值。

它应该是 sqlalchemy.orm.attributes.InstrumentedAttribute,但是当我运行测试时它是常量值,比如说字符串。

有一些方法可以在调试模式下运行 python 程序,并在每一步通过代码行后自动运行一些检查(如果变量改变了类型)?

PS我知道如何在检查和属性装饰器的帮助下记录类实例属性的变化。可能在这里我可以将此方法与元类一起使用...

但有时我需要更通用和更强大的解决方案......

谢谢你。

PPS 我需要类似的东西:https ://stackoverflow.com/a/7669165/816449 ,但可能会对该代码中发生的事情进行更多解释。

4

6 回答 6

13

好吧,这是一种缓慢的方法。可以修改它以观察局部变量的变化(仅按名称)。下面是它的工作原理:我们做 sys.settrace 并分析 obj.attr 每一步的值。棘手的部分是我们'line'在执行 line 之前接收事件(执行了某些 line)。因此,当我们注意到 obj.attr 发生了变化时,我们已经在下一行并且我们无法获取上一行的帧(因为没有为每一行复制帧,它们被修改了)。traceback.format_stack因此,在我保存到的每一行事件中watcher.prev_st,如果在下一次调用trace_command值时发生了变化,我们将保存的堆栈跟踪打印到文件中。在每一行上保存回溯是一项非常昂贵的操作,因此您必须设置include将关键字添加到项目目录列表(或只是项目的根目录)中,以免看到其他库如何做他们的工作并浪费 cpu。

观察者.py

import traceback

class Watcher(object):
    def __init__(self, obj=None, attr=None, log_file='log.txt', include=[], enabled=False):
        """
            Debugger that watches for changes in object attributes
            obj - object to be watched
            attr - string, name of attribute
            log_file - string, where to write output
            include - list of strings, debug files only in these directories.
               Set it to path of your project otherwise it will take long time
               to run on big libraries import and usage.
        """

        self.log_file=log_file
        with open(self.log_file, 'wb'): pass
        self.prev_st = None
        self.include = [incl.replace('\\','/') for incl in include]
        if obj:
            self.value = getattr(obj, attr)
        self.obj = obj
        self.attr = attr
        self.enabled = enabled # Important, must be last line on __init__.

    def __call__(self, *args, **kwargs):
        kwargs['enabled'] = True
        self.__init__(*args, **kwargs)

    def check_condition(self):
        tmp = getattr(self.obj, self.attr)
        result = tmp != self.value
        self.value = tmp
        return result

    def trace_command(self, frame, event, arg):
        if event!='line' or not self.enabled:
            return self.trace_command
        if self.check_condition():
            if self.prev_st:
                with open(self.log_file, 'ab') as f:
                    print >>f, "Value of",self.obj,".",self.attr,"changed!"
                    print >>f,"###### Line:"
                    print >>f,''.join(self.prev_st)
        if self.include:
            fname = frame.f_code.co_filename.replace('\\','/')
            to_include = False
            for incl in self.include:
                if fname.startswith(incl):
                    to_include = True
                    break
            if not to_include:
                return self.trace_command
        self.prev_st = traceback.format_stack(frame)
        return self.trace_command
import sys
watcher = Watcher()
sys.settrace(watcher.trace_command)

测试观察者.py

from watcher import watcher
import numpy as np
import urllib2
class X(object):
    def __init__(self, foo):
        self.foo = foo

class Y(object):
    def __init__(self, x):
        self.xoo = x

    def boom(self):
        self.xoo.foo = "xoo foo!"
def main():
    x = X(50)
    watcher(x, 'foo', log_file='log.txt', include =['C:/Users/j/PycharmProjects/hello'])
    x.foo = 500
    x.goo = 300
    y = Y(x)
    y.boom()
    arr = np.arange(0,100,0.1)
    arr = arr**2
    for i in xrange(3):
        print 'a'
        x.foo = i

    for i in xrange(1):
        i = i+1

main()
于 2012-11-15T19:40:06.133 回答
2

尝试使用__setattr__覆盖尝试进行属性分配时调用的函数。文档___setattr__

于 2016-01-13T20:15:15.227 回答
2

监视对象属性更改(也可以是模块级变量或任何可访问的对象)的更简单方法getattr是利用hunter库,这是一个灵活的代码跟踪工具包。为了检测状态变化,我们需要一个如下所示的谓词:

import traceback


class MutationWatcher:

    def __init__(self, target, attrs):
        self.target = target
        self.state = {k: getattr(target, k) for k in attrs}

    def __call__(self, event):
        result = False
        for k, v in self.state.items():
            current_value = getattr(self.target, k)
            if v != current_value:
                result = True
                self.state[k] = current_value
                print('Value of attribute {} has chaned from {!r} to {!r}'.format(
                    k, v, current_value))

        if result:
            traceback.print_stack(event.frame)

        return result

然后给出一个示例代码:

class TargetThatChangesWeirdly:
    attr_name = 1


def some_nested_function_that_does_the_nasty_mutation(obj):
    obj.attr_name = 2


def some_public_api(obj):
    some_nested_function_that_does_the_nasty_mutation(obj)

我们可以使用以下方法对其进行检测hunter

# or any other entry point that calls the public API of interest
if __name__ == '__main__':
    obj = TargetThatChangesWeirdly()

    import hunter
    watcher = MutationWatcher(obj, ['attr_name'])
    hunter.trace(watcher, stdlib=False, action=hunter.CodePrinter)

    some_public_api(obj)

运行该模块会产生:

Value of attribute attr_name has chaned from 1 to 2
  File "test.py", line 44, in <module>
    some_public_api(obj)
  File "test.py", line 10, in some_public_api
    some_nested_function_that_does_the_nasty_mutation(obj)
  File "test.py", line 6, in some_nested_function_that_does_the_nasty_mutation
    obj.attr_name = 2
                                 test.py:6     return        obj.attr_name = 2
                                               ...       return value: None

你也可以使用其他支持action的s hunter。例如, Debugger它闯入pdb(属性更改的调试器)。

于 2019-02-05T14:45:30.630 回答
2

有一个非常简单的方法可以做到这一点:使用watchpoints

基本上你只需要做

from watchpoints import watch
watch(your_object.attr)

就是这样。每当更改属性时,它都会打印出更改它的行以及它是如何更改的。超级容易使用。

它还具有更高级的功能,例如,您可以在变量更改时调用 pdb,或者使用自己的回调函数而不是将其打印到 stdout。

于 2020-12-09T07:40:38.393 回答
0

您可以使用python 调试器模块(标准库的一部分)

要使用,只需在源文件顶部导入 pdb:

import pdb

然后在要开始检查代码的任何位置设置跟踪:

pdb.set_trace()

然后,您可以使用 单步执行代码n,并通过运行 python 命令调查当前状态。

于 2012-11-15T17:33:45.313 回答
0
def __setattr__(self, name, value):
    if name=="xxx":
       util.output_stack('xxxxx')
    super(XXX, self).__setattr__(name, value)

这个示例代码帮助了我。

于 2019-12-06T07:15:33.617 回答