Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python : Adding a code routine at each line of a block of code

I want to have a piece of code run after each line of another block of code. For example, want to be able to evaluate a global variable before or after executing the next line of a function.

For example, below I try to print 'hello' before each line of the foo() function. I think that a decorator can help me but it would need some introspection feature in order to edit each line of my foo() function and add what I want before or after it.

I am trying to perform something like this :

>>> def foo():
...    print 'bar'
...    print 'barbar'
...    print 'barbarbar'

>>> foo()
hello
bar
hello
barbar 
hello
barbarbar

How can I perform this? would the __code__ object help? do I need a decorator & introspection at the same time?

EDIT: Here is another example of the goal of this thread:

>>> def foo():
...    for i in range(0,3):
...        print 'bar'

>>> foo()
hello
bar
hello
bar 
hello
bar

In this new case, before printing each "bar", I want to print a "hello".

The main goal of this is to be able to execute another function or test any kind of global variable before executing the next line of the code. Imagine if a global variable is True, then the code goes to the next line; while if the global variable is False, then it stops the function execution.

EDIT: In a way, i am looking for a tool to inject code inside another block of code.

EDIT: Thank's to unutbu i have achieved this code :

import sys
import time
import threading

class SetTrace(object):
    """
    with SetTrace(monitor):
    """
    def __init__(self, func):
        self.func = func
    def __enter__(self):
        sys.settrace(self.func)
        return self
    def __exit__(self, ext_type, exc_value, traceback):
        sys.settrace(None)
        # http://effbot.org/zone/python-with-statement.htm
        # When __exit__ returns True, the exception is swallowed.
        # When __exit__ returns False, the exception is reraised.
        # This catches Sentinel, and lets other errors through
        # return isinstance(exc_value, Exception)

def monitor(frame, event, arg):
    if event == "line":
        if not running:
            raise Exception("global running is False, exiting")
    return monitor

def isRunning(function):
    def defaultBehavior(*args):
        with SetTrace(monitor):
            ret = function(*args)
            return ret
    return defaultBehavior

@isRunning
def foo():
    while True:
        time.sleep(1)
        print 'bar'

global running
running = True
thread = threading.Thread(target = foo)
thread.start()
time.sleep(3)
running = False
like image 205
afiah Avatar asked Jul 02 '14 12:07

afiah


1 Answers

Perhaps you are looking for sys.settrace:

import sys
class SetTrace(object):
    def __init__(self, func):
        self.func = func

    def __enter__(self):
        sys.settrace(self.func)
        return self

    def __exit__(self, ext_type, exc_value, traceback):
        sys.settrace(None)

def monitor(frame, event, arg):
    if event == "line":
        print('hello')
        # print(frame.f_globals) 
        # print(frame.f_locals)  
    return monitor



def foo():
   print 'bar'
   print 'barbar'
   print 'barbarbar'

with SetTrace(monitor):
    foo()

yields

hello
bar
hello
barbar
hello
barbarbar
hello

Inside monitor, you can access the foo's locals and globals using frame.f_locals and frame.f_globals.

See this post for an example of how sys.settrace can be used for debugging.


How to stop foo from within monitor:

The most graceful way to do this would be to place a conditional statement inside foo so that foo checks when to exit. Then you can manipulate the value of the conditional from within monitor to control when foo exits.

However, if you do not want to or can not change foo, then an alternative is to raise an exception from within monitor. The exception will bubble up through the frame stack until it is caught. If you catch it in SetTrace.__exit__, then the flow of control will continue as though foo had just exited.

import sys
class Sentinel(Exception): pass

class SetTrace(object):
    """
    with SetTrace(monitor):
        ...
    """
    def __init__(self, func):
        self.func = func

    def __enter__(self):
        sys.settrace(self.func)
        return self

    def __exit__(self, ext_type, exc_value, traceback):
        sys.settrace(None)
        # http://effbot.org/zone/python-with-statement.htm
        # When __exit__ returns True, the exception is swallowed.
        # When __exit__ returns False, the exception is reraised.

        # This catches Sentinel, and lets other errors through
        return isinstance(exc_value, Sentinel)

def monitor(frame, event, arg):
    if event == "line":
        l = frame.f_locals
        x = l.get('x', 0)
        print('x = {}'.format(x))
        if x > 3:
            raise Sentinel()
    return monitor

def foo():
    x = 0
    while True:
        print 'bar'
        x += 1

with SetTrace(monitor):
    foo()
like image 69
unutbu Avatar answered Sep 20 '22 12:09

unutbu