如何在没有 dunder setattr 或 pdb 的情况下观察 python 中的变量变化 [英] How to watch for a variable change in python without dunder setattr or pdb

查看:15
本文介绍了如何在没有 dunder setattr 或 pdb 的情况下观察 python 中的变量变化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

There is large python project where one attribute of one class just have wrong value in some place.

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

It should be sqlalchemy.orm.attributes.InstrumentedAttribute, but when I run tests it is constant value, let's say string.

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

There is some way to run python program in debug mode, and run some check (if variable changed type) after each step throught line of code automatically?

附言我知道如何在检查和属性装饰器的帮助下记录类实例的属性更改.可能在这里我可以将这种方法与元类一起使用...

P.S. I know how to log changes of attribute of class instance with help of inspect and property decorator. Possibly here I can use this method with metaclasses...

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

But sometimes I need more general and powerfull solution...

谢谢.

P.P.S.我需要类似的东西:https://stackoverflow.com/a/7669165/816449,但可能会有更多解释代码中发生了什么.

P.P.S. I need something like there: https://stackoverflow.com/a/7669165/816449, but may be with more explanation of what is going on in that code.

推荐答案

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

Well, here is a sort of slow approach. It can be modified for watching for local variable change (just by name). Here is how it works: we do sys.settrace and analyse the value of obj.attr each step. The tricky part is that we receive 'line' events (that some line was executed) before line is executed. So, when we notice that obj.attr has changed, we are already on the next line and we can't get the previous line frame (because frames aren't copied for each line, they are modified ). So on each line event I save traceback.format_stack to watcher.prev_st and if on the next call of trace_command value has changed, we print the saved stack trace to file. Saving traceback on each line is quite an expensive operation, so you'd have to set include keyword to a list of your projects directories (or just the root of your project) in order not to watch how other libraries are doing their stuff and waste 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()

这篇关于如何在没有 dunder setattr 或 pdb 的情况下观察 python 中的变量变化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆