2

I am trying to write a function debug decorator that will look at:

def foo(baz):
  bar = 1
  bar = 2
  return bar

and wrap it to:

def foo(baz):
  bar = 1
  print 'bar: {}'.format(bar)
  bar = 2
  print 'bar: {}'.format(bar)
  return bar

I need to play with the function as text, to grab "\w+(?=\s*[=])", but do not know how to access that. I have a decorator I modified from a blog that works, but I just tried changing it to:

class decorator_string_check(object):

   def __init__(self, func):
        self.func = func
        wraps(func)(self)

   def __call__(self, *args, **kwargs):
        print dir(self.func)
        print dir(self.func.__code__)
        print self.func.__code__.__str__()
        ret = self.func(*args, **kwargs)
        return ret

@decorator_string_check
def fake(x):
    y = 6
    y = x
    return y

y = fake(9)

and am getting nothinng of value, namely:

['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
<code object fake at 0x7f98b8b1d030, file "./logging.py", line 48>

How do I work with the actual "func" text, to run regexes on it and find things I need within a decorator class object? Thank you

codyc4321
  • 9,014
  • 22
  • 92
  • 165

1 Answers1

6

First of all, I suggest you do not do something like that. It's hard to get working code and very hard to make a correct version.

Besides I don't really know what you want to do exactly. Should this decorator add a print statement after every assignment and show the updated value? Or keep track only of a given subset of variables?

This said, to obtain the source code of something you can use the inspect module in particular the getsource function:

In [1]: def test():
   ...:     a = 1
   ...:     b = 2
   ...:     

In [2]: import inspect

In [3]: inspect.getsource(test)
Out[3]: 'def test():\n    a = 1\n    b = 2\n'
In [4]: print(inspect.getsource(test))
def test():
    a = 1
    b = 2

You could modify and inspect the source code as you wish and finally compile() the new source code.

Note however that:

  • You have to be careful when modifying the source code, because it's easy to create syntactically invalid code (think: multiline expressions etc.)
  • when compiling you'd like to compile in the same scope as the original function. The inspect module has some functions that allow you to obtain the stackframe where your decorator is called and you can obtain the environments from there. Read here about how to handle the stack.
  • The source code may not be available. The code could be compiled in bytecode and the original file may be deleted. In this case getsource would simply raise an OSError.

A more "sane" solution would be to not look at the source code, but at the bytecode. You can do this using the dis module. You may try to see when the values of the variable change and insert some bytecode that will print that variable.

Note that the dis module was greatly enhanced in python3.4+, so with previous versions of python this would probably be hard.

You should probably read articles like Python bytecode hacks, gotos revisited before trying this. They give you an idea on how to look at bytecode and work with it.

This is probably safer (e.g. even if the source file doesn't exist on the machine the bytecode will still be accessible), yet I still think what you have in mind is not a good thing to do, except as an exercise.


As jsbueno points out the proper way of doing what you want (i.e. a python debugger) is to use sys.settrace.

This function allows you to set a tracing function which will be called for each "code" executed. The function will know when a function is called, a new block is entered etc. It gives you access to the frame where the code will be executed, and thus you should be able to find the values you are interested in.

You should check the lnotab_notes.txt file to understand how the data provided as argument to this function can be mapped to the source code positions to understand when an assignment is performed.

When I have time (probably next week end) I'll try to implement something based on this approach to demonstrate it.

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • 1
    Actually a sane solution at all (not just a "more sane") is to use python debugging facilities -with `sys.settrace`, and effectively write a custom debugger which will compare the local variables values after each line, and print the different ones - it should not be hard to come up with a decorator that will enable `settrace` propoerly for the duration of the decorated code. – jsbueno Jun 19 '15 at 20:29
  • 2
    @jsbueno You are right. I didn't know about it and it *does* look the only proper way of doing something like that. I have added it to my answer. – Bakuriu Jun 20 '15 at 06:53
  • @Bakuriu doumo arigatou gozaimasu...anata wa watashi yori mo kashikoku miemasu :) I am wanting to keep track of all/every variable assignments, just to know exactly what the variables were at that point in time. That way I could decorate something that isn't working instead of the 1238234 print statement method. I'm sure the whole world could use our decorator – codyc4321 Jun 23 '15 at 13:54
  • "Should this decorator add a print statement after every assignment and show the updated value?" yes that's what I was goin for – codyc4321 Jun 23 '15 at 13:56