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



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

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

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

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

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

谢谢。

附言:我需要这样的东西:https://stackoverflow.com/a/7669165/816449,但可能会对该代码中的内容进行更多解释。

这里有一种慢速方法。可以对其进行修改以观察局部变量的变化(仅通过名称)。它的工作原理如下:我们执行sys.settirace并分析每一步obj.attr的值。棘手的部分是,我们在执行某行之前接收'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)

testwatchr.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()

有一个非常简单的方法:使用观察点。

基本上你只需要做

from watchpoints import watch
watch(your_object.attr)

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

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

监视对象属性更改(也可以是模块级变量或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

您也可以使用hunter支持的其他action。例如,Debugger,它分解为pdb(属性更改的调试器)。

尝试使用__setattr__覆盖尝试属性分配时调用的函数。__setattr__文件

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

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

import pdb

然后在任何你想开始检查代码的地方设置一个跟踪:

pdb.set_trace()

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

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

这个示例代码帮助了我。

相关内容

最新更新