注意python中的变量

时间:2012-11-15 17:21:02

标签: python debugging introspection pdb

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

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

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

P.S。我知道如何在inspect和property decorator的帮助下记录类实例属性的更改。可能在这里我可以使用这种方法与元类...

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

谢谢。

P.P.S。我需要类似的内容:https://stackoverflow.com/a/7669165/816449,但可能会对该代码中发生的事情做出更多解释。

6 个答案:

答案 0 :(得分:12)

嗯,这是一种方法。可以修改它以查看局部变量(仅按名称)。以下是它的工作原理:我们执行sys.settrace并分析每一步的obj.attr值。棘手的部分是我们在执行行之前收到'line'个事件(执行某些行)。因此,当我们注意到obj.attr已经改变时,我们已经在下一行,并且我们无法获得前一个行帧(因为没有为每一行复制帧,它们被修改)。因此,在每个行事件中,我将traceback.format_stack保存到watcher.prev_st,如果在trace_command的下一次调用值已更改,我们将保存的堆栈跟踪打印到文件。在每一行保存回溯是一项非常昂贵的操作,因此您必须将include关键字设置为项目目录列表(或只是项目的根目录),以便不查看其他库如何执行他们的东西和垃圾cpu。

<强> watcher.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)

<强> testwatcher.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()

答案 1 :(得分:1)

您可以使用python debugger module(标准库的一部分)

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

import pdb

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

pdb.set_trace()

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

答案 2 :(得分:1)

尝试使用__setattr____setattr__

Documentation

答案 3 :(得分:1)

监视对象属性更改(它也可以是模块级变量或可以用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支持的其他hunter。例如,Debugger会分成pdb(属性更改时的调试器)。

答案 4 :(得分:0)

def __setattr__(self, name, value):
    if name=="xxx":
       util.output_stack('xxxxx')
    super(XXX, self).__setattr__(name, value)

此示例代码对我有所帮助。

答案 5 :(得分:0)

有一种非常简单的方法:使用watchpoints

基本上你只需要做

from watchpoints import watch
watch(your_object.attr)

就是这样。每当更改属性时,它将打印出更改属性的行以及更改方式。超级好用。

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