问题 注意python中的变量


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

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

有一些方法可以在调试模式下运行python程序,并在每个步骤之后自动执行一些检查(如果变量更改类型)吗?

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

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

谢谢。

P.P.S.我需要这样的东西: https://stackoverflow.com/a/7669165/816449,但可能更多地解释该代码中发生的事情。


3049
2017-11-15 17:21


起源



答案:


嗯,这是一种  做法。可以修改它以查看局部变量(仅按名称)。以下是它的工作原理:我们执行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()

12
2017-11-15 19:40



是的,这很慢,但仍比手动pdb快,谢谢。 - Bunyk
是的,修好了。顺便说一句,如果您对下一行而不是实际行没问题,可以通过更快的方式完成,请查看: gist.github.com/4086770 它显示下一行或实际一行,具体取决于是否 line 事件发生在 line 事件 - alex_jordan


答案:


嗯,这是一种  做法。可以修改它以查看局部变量(仅按名称)。以下是它的工作原理:我们执行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()

12
2017-11-15 19:40



是的,这很慢,但仍比手动pdb快,谢谢。 - Bunyk
是的,修好了。顺便说一句,如果您对下一行而不是实际行没问题,可以通过更快的方式完成,请查看: gist.github.com/4086770 它显示下一行或实际一行,具体取决于是否 line 事件发生在 line 事件 - alex_jordan


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

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

import pdb

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

pdb.set_trace()

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


1
2017-11-15 17:33



对不起,我忘了添加,我希望这个想法能够自动运行。所以我启动调试器,给它我的条件,例如type(some.module.SomeClass.my_attribute)== str),并找到条件不满足的第一行。并且有数百万行代码,我不知道变量的位置。 - Bunyk


尝试使用 __setattr__文档 对于 __setattr__


1
2018-01-13 20:15



这里有一点需要注意 - 这只适用于定义类的实例的属性 __setattr__。要将它与类属性一起使用,我们需要为类重新定义元类,并且谁知道我们需要使用什么魔法才能使它与模块中定义的变量一起工作。 - Bunyk
只是一个建议。 - Solomon Ucko