如何在Python中跟踪局部变量的值? [英] How can I track the values of a local variable in Python?

查看:231
本文介绍了如何在Python中跟踪局部变量的值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的算法遍历了几秒钟的数据.对于每个数据第二价值,我都返回一个值,这是一个涉及多个子模块和子类的相当复杂的计算的结果.其中某些类每秒都会重新初始化.

My algorithm loops over seconds of data. For each second-worth-of-data, I return a value, that is the result of a fairly involved computation involving multiple submodules and subclasses. Some of these classes are re-initialized each second.

出于调试目的,有几次我一直想绘制一段时间内这些类之一中某个局部变量的值.外部的东西将不得不序列化并记录这些值,因为该类仅存在一秒钟.每次都是不同的局部变量.

For debugging purposes, a few times I have been in the situation that I have wanted to plot the value of a certain local variable in one of these classes over time. Something external would have to serialize and log the values, because the class exists for only a second. It has been a different local variable each time.

我如何才能在软件设计方面正确实现目标,而又不会每次都花费我几个小时,并且每次我都不想编写一两行以上的新代码?

理想情况下,我考虑过的一种解决方案是拥有诸如全局IO流之类的东西,而不必初始化"每个类,这样我就可以随处可见",因此我可以插入某种类型的东西在代码中的任何位置调用MySerializer << [mylocalvariable, timestamp]命令,然后在运行结束时检查MySerializer是否为空,如果不是,则可以绘制其中的内容...或类似内容.如果我可以对不同类中的多个局部变量执行此操作,则更好.这个解决方案好吗?我该怎么办?

Ideally, one solution I have considered would be to have something like a global IO stream, or something like that, that I would "just be around" without having to initialize each class with it, so I could just insert some sort of MySerializer << [mylocalvariable, timestamp] command at any point in the code, and then when the run finished I could check if MySerializer is empty and if not I could plot what what is in it... or something like that. Better still if I could do this for multiple local variables in different classes. Would this solution be good? How could I do this?

或者为了能够以一种面向方面的方式做到这一点,可以使某些外部对象查看代码"而不进行更改,构建该局部变量的值的缓冲区,然后将它们随处可见.结尾.我该怎么办?

Or to be able to do this in an aspect-oriented way, with some outside object "looking at the code" without changing it, building a buffer of values of that local variable, and spitting them out to a plot in the end. How might I do this?

是否有更好的解决方案?哪种设计模式适合这种情况?

Is there a better solution that either of these? Which design patterns suit this situation?

我过去所做的是将局部变量返回给拥有函数的人,然后该人又必须返回其接收到的值,依此类推,一直到最上层. .这是一团糟,每次都必须写入和删除.

What I have done is in the past is to return that local variable to whoever holds the function, who then in turn has to return that value it has received, and so on and so forth, all the way up to the top. This is a huge mess and has to be written and deleted each time.

推荐答案

我想到的是这样一个非常简单的东西:

I had in mind something really simple like this:

#the decorator
def debug_function(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print('debug:', res)
        return res

    return wrapper


#proof of concept:
@debug_function
def square(number):
    return number*number

class ClassA:
    def __init__(self):
        self.Number = 42

    @debug_function
    def return_number(self):
        return self.Number


if __name__ == '__main__':
    result = [square(i) for i in range(5)]
    print(result)

    my_obj = ClassA()
    n = my_obj.return_number()
    print(n)

简而言之,编写一个简单的装饰器,将函数的结果记录到某个地方(上面我仅将其写到终端上,但是可以扩展为使用日志文件或类似文件).然后,您可以装饰要跟踪的任何函数,并在调用该函数时获取其返回值.在上面的代码中,我展示了它对简单函数和类方法的作用.示例代码的结果如下所示:

In short, write a simple decorator that logs the result of your function somewhere (above I only write it out to the terminal, but this could be extended to use a log file or similar). Then you decorate whatever function you want to track and will get its return value whenever the function is called. In the code above I show what it does for a simple function and a class method. The result of the example code looks like this:

debug: 0
debug: 1
debug: 4
debug: 9
debug: 16
[0, 1, 4, 9, 16]
debug: 42
42

编辑2 :

我编辑了以下代码,以使用实际功能而不是仅使用__name__来存储中间值.这样可以减少出错的可能性.

I edited the code below to use the actual function instead of just its __name__ to store the intermediate values. This should make it somewhat less error prone.

编辑:

为了将值存储在内存中,我将再次尽可能地简单,仅将值存储在列表中.对于上面概述的简单示例,可能有一个全局列表对象就足够了.但是,由于您很可能希望一次查看多个函数,因此我建议将装饰器设计为一个类,并将每个函数的一个列表存储在class属性中.在示例代码中对此有更多的了解.

For storing values in memory, I would again go as simple as possible and just store the values in a list. For the simple example outlined above, possibly a global list object would be enough. However, as you most likely want to look at more than one function at a time, I'd rather suggest to design the decorator as a class and store one list per function in a class attribute. More about this in the example code.

真正的问题是局部变量的存储.为此,您必须更改函数的实际代码.自然,您不想手动"执行此操作,而是希望您的装饰员来处理此操作.在这里变得棘手.环顾了一会后,我发现了一个名为字节码的程序包(至少适用于Python 3.6).还有最可能的其他选择,但我决定采用这一选择. bytecode允许您将python字节码转换为人类可读的形式,对其进行修改,然后将其转换回python字节码.我不得不承认,我在这里不够深入,但是我要做的是编写一些小函数,查看翻译后的代码,并设计出符合我想要的代码.

The real problem is the storing of local variables. In order to do this you have to change the actual code of your function. Naturally, you don't want to do this 'by hand', but want your decorator to take care of this. Here it becomes tricky. After looking around for a while, I found a package called bytecode (which works at least for Python 3.6). There are most likely other options, but I decided to go with this one. bytecode allows you to translate the python bytecode into human-readable form, modify it, and translate it back to python bytecode. I have to admit that I'm a bit out of my depth here, but what I did was to write a few small functions, look at the translated code and design a piece of code that does what I want.

因此,在此示例中,目标是装饰要测试的函数,以使装饰器将字符串列表作为参数,其中每个字符串都是应跟踪的变量的名称.然后,它将代码添加到函数主体中,该函数主体将所有列出的变量的 final 值包装在一个元组中,并将元组与实际返回值一起返回.然后,包装器"函数收集跟踪的值,并将它们附加到特定于函数的值列表中,这些值可以在代码中的任何位置读取.

So, in this example the objective is to decorate the function to be tested, such that the decorator takes a list of strings as argument, where each string is the name of a variable that should be tracked. It then adds code to the function body that packs the final values of all listed variables in a tuple and returns the tuple together with the real return value. The 'wrapper' function then collects the tracked values and appends them to a function-specific list of values which can be read at any point in the code.

就这样.将实际的装饰器放在其自己的文件中,在这里将其命名为debug_function.py:

So here it goes. Put the actual decorator in its own file, I call it here debug_function.py:

from bytecode import Bytecode, Instr

class debug_function(object):
    """
    Decorator that takes a list of variable names as argument. Everytime
    the decorated function is called, the final states of the listed
    variables are logged and can be read any time during code execution.
    """
    _functions = {}
    def __init__(self, varnames):
        self.varnames = varnames


    def __call__(self, func):
        print('logging variables {} of function {}'.format(
            ','.join(self.varnames), func.__name__
        ))
        debug_function._functions[func] = []
        c = Bytecode.from_code(func.__code__)
        extra_code = [
            Instr('STORE_FAST', '_res')
        ]+[
            Instr('LOAD_FAST', name) for name in self.varnames
        ]+[
            Instr('BUILD_TUPLE', len(self.varnames)),
            Instr('STORE_FAST', '_debug_tuple'),
            Instr('LOAD_FAST', '_res'),
            Instr('LOAD_FAST', '_debug_tuple'),
            Instr('BUILD_TUPLE', 2),
            Instr('STORE_FAST', '_result_tuple'),
            Instr('LOAD_FAST', '_result_tuple'),
        ]
        c[-1:-1]= extra_code
        func.__code__=c.to_code()

        def wrapper(*args, **kwargs):
            res, values = func(*args, **kwargs)
            debug_function._functions[func].append(values)
            return res

        return wrapper

    @staticmethod
    def get_values(func):
        return debug_function._functions[func]

然后,让我们再次生成一些要检查的函数,这些函数将使用此装饰器进行装饰.例如,将它们放在functions.py

Then, let's generate again some functions to be checked, which we decorate with this decorator. Put these, for instance, in functions.py

from debug_function import debug_function

@debug_function(['c','d'])
def test_func(a,b):
    c = a+b
    d = a-b
    return c+d


class test_class:
    def __init__(self, value):
        self.val = value

    @debug_function(['y'])
    def test_method(self, *args):
        x = sum(args)
        y = 1
        for arg in args:
            y*=arg
        return x+y

最后,调用函数并查看输出. debug_function有一个称为get()的静态方法,该方法将您要获取信息的函数作为参数,并返回一个元组列表.每个元组包含一次调用该函数后要跟踪的所有局部变量的 final 值.这些值的顺序与在decorator语句中列出的顺序相同.使用'inverse'zip,您可以轻松地分隔这些元组.

Finally, call the functions and look at the output. debug_function has a static method called get(), which takes the function you want information on as argument and returns a list of tuples. Each of these tuples contains the final values of all the local variables you wanted to track after one call to that function. The values are in the same order in which they were listed in the decorator statement. With an 'inverse' zip, you can easily separate these tuples.

from debug_function import debug_function
from functions import test_func, test_class

results = [test_func(i,j) for i in range(5) for j in range(8,12)]
c,d = zip(*debug_function.get_values(test_func))
print('results:', results)
print('intermediate values:')
print('c =', c)
print('d =', d)

my_class = test_class(7)
results2 = [
    my_class.test_method(i,j,4,2) for i in range(5) for j in range(8,12)
]
y, = zip(*debug_function.get_values(test_class.test_method))
print('results:', results2)
print('intermediate values:')
print('y =', y)

呼叫的输出如下:

logging variables c,d of function test_func
logging variables y of function test_method
results: [0, 0, 0, 0, 2, 2, 2, 2, 4, 4, 4, 4, 6, 6, 6, 6, 8, 8, 8, 8]
intermediate values:
c = (8, 9, 10, 11, 9, 10, 11, 12, 10, 11, 12, 13, 11, 12, 13, 14, 12, 13, 14, 15)
d = (-8, -9, -10, -11, -7, -8, -9, -10, -6, -7, -8, -9, -5, -6, -7, -8, -4, -5, -6, -7)
results: [14, 15, 16, 17, 79, 88, 97, 106, 144, 161, 178, 195, 209, 234, 259, 284, 274, 307, 340, 373]
intermediate values:
y = (0, 0, 0, 0, 64, 72, 80, 88, 128, 144, 160, 176, 192, 216, 240, 264, 256, 288, 320, 352)

我可能应该解释一下它是如何工作的,请问是否有任何不清楚的地方.如前所述,此修饰符仅存储每个变量的 final 值(即在执行功能代码后变量具有的值).如果您具有更复杂的功能,则可能会对值的含义感兴趣,例如每个变量赋值-在这种情况下,您将需要做更多的工作,但它应该是可行的.

I probably should explain a bit better how this works, please ask if anything stayed unclear. As said before, this decorator only stores the final value of each variable (i.e. the value that variable has after the function code has been executed). If you have a more complex function, you may be interested what the value is at, e.g., each variable assignment -- in this case you'll have to do a bit more work, but it should be doable.

希望这会有所帮助

这篇关于如何在Python中跟踪局部变量的值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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