如何在没有dunder setattr或pdb的情况下观察python中的变量更改

如何在没有dunder setattr或pdb的情况下观察python中的变量更改,python,debugging,introspection,pdb,Python,Debugging,Introspection,Pdb,有一个大型python项目,其中一个类的一个属性在某个地方有错误的值 它应该是sqlalchemy.orm.attributes.InstrumentedAttribute,但当我运行测试时,它是常量值,比如字符串 有什么方法可以在调试模式下运行python程序,并在代码行的每一步之后自动运行一些检查(如果变量类型发生了变化) 另外,我知道如何在inspect和property decorator的帮助下记录类实例属性的更改。可能在这里我可以用元类的这个方法 但有时我需要更一般和强大的解决方案

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

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

有什么方法可以在调试模式下运行python程序,并在代码行的每一步之后自动运行一些检查(如果变量类型发生了变化)

另外,我知道如何在inspect和property decorator的帮助下记录类实例属性的更改。可能在这里我可以用元类的这个方法

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

多谢各位

p.p.S.我需要这样的东西:,但可能需要对代码中发生的情况进行更多解释。

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

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

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

pdb.set_trace()
然后,您可以使用
n
逐步浏览代码,并通过运行python命令来研究当前状态。

好吧,这里是一种缓慢的方法。可以修改它以观察局部变量的变化(仅按名称)。它的工作原理如下:我们对每个步骤进行sys.settrace并分析obj.attr的值。棘手的是,在执行行之前,我们会收到
'line'
事件(某些行被执行)。因此,当我们注意到obj.attr已更改时,我们已经在下一行上,无法获取上一行帧(因为没有为每一行复制帧,所以会修改帧)。因此,在每一行事件上,我保存
traceback.format_stack
watcher.prev_st
,如果在下一次调用
trace_命令时更改了值,我们将保存的堆栈跟踪打印到文件中。在每行上保存回溯是一项非常昂贵的操作,因此您必须将
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)
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()
testwatcher.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)
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()

尝试使用
\uuuu setattr\uuuu
覆盖在尝试属性分配时调用的函数。对于
\uuuu setattr\uuuuuu

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

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
属性attr_name的值已从1更改为2 文件“test.py”,第44行,在 一些公共api(obj) 文件“test.py”,第10行,在一些公共api中 一些嵌套的函数,它会导致突变(obj) 文件“test.py”,第6行,在某个嵌套函数中,该函数进行了变异 obj.attr_name=2 test.py:6返回obj.attr_name=2 ... 返回值:无
您还可以使用
hunter
支持的其他
action
s。例如,它分为
pdb
(属性更改时的调试器)


这个示例代码帮助了我。

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

基本上你只需要做

from watchpoints import watch
watch(your_object.attr)
就这样。无论何时更改属性,它都会打印出更改该属性的行及其更改方式。超级容易使用


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

对不起,我忘记添加了,我希望此功能能够自动工作。所以我启动调试器,给它我的条件,例如type(some.module.SomeClass.my_属性)==str),然后找到不满足条件的第一行。有数百万行代码,我不知道变量在哪里被更改。是的,这非常慢,但仍然比手动pdb快,谢谢。是的,修复了。顺便说一下,如果你对下一行而不是实际行没有意见,可以用一种更快的方式来完成,看看这个:它显示下一行或实际行,根据
事件是否跟在
事件之后,这里有一点需要注意-这只适用于定义
\uuuuuuuuu setattr\uuuuuu
的类实例的属性。要将它与类属性一起使用,我们需要为类重新定义元类,谁知道我们需要什么魔法才能使它与模块中定义的变量一起工作。