# persistent_locals2 has been co-authored with Andrea Maffezzoli
class persistent_locals2(object):
def __init__(self, func):
self._locals = {}
self.func = func
def __call__(self, *args, **kwargs):
def tracer(frame, event, arg):
if event=='return':
self._locals = frame.f_locals.copy()
# tracer is activated on next call, return or exception
sys.setprofile(tracer)
try:
# trace the function call
res = self.func(*args, **kwargs)
finally:
# disable tracer and replace with old one
sys.setprofile(None)
return res
def clear_locals(self):
self._locals = {}
@property
def locals(self):
return self._locals
Diff to Previous Revision
--- revision 1 2010-07-07 16:41:16
+++ revision 2 2010-07-07 16:48:43
@@ -1,99 +1,27 @@
-import new
-import byteplay as bp
-import inspect
-
-def persistent_locals(f):
- """Function decorator to expose local variables after execution.
-
- Modify the function such that, at the exit of the function
- (regular exit or exceptions), the local dictionary is copied to a
- read-only function property 'locals'.
-
- This decorator wraps the function in a callable object, and
- modifies its bytecode by adding an external try...finally
- statement equivalent to the following:
-
- def f(self, *args, **kwargs):
- try:
- ... old code ...
- finally:
- self._locals = locals().copy()
- del self._locals['self']
- """
-
- # ### disassemble f
- f_code = bp.Code.from_code(f.func_code)
-
- # ### use bytecode injection to add try...finally statement around code
- finally_label = bp.Label()
- # try:
- code_before = (bp.SETUP_FINALLY, finally_label)
- # [original code here]
- # finally:
- code_after = [(finally_label, None),
- # self._locals = locals().copy()
- (bp.LOAD_GLOBAL, 'locals'),
- (bp.CALL_FUNCTION, 0),
- (bp.LOAD_ATTR, 'copy'),
- (bp.CALL_FUNCTION, 0),
- (bp.LOAD_FAST, 'self'),
- (bp.STORE_ATTR, '_locals'),
- # del self._locals['self']
- (bp.LOAD_FAST, 'self'),
- (bp.LOAD_ATTR, '_locals'),
- (bp.LOAD_CONST, 'self'),
- (bp.DELETE_SUBSCR, None),
- (bp.END_FINALLY, None),
- (bp.LOAD_CONST, None),
- (bp.RETURN_VALUE, None)]
-
- f_code.code.insert(0, code_before)
- f_code.code.extend(code_after)
-
- # ### re-assemble
- f_code.args = ('self',) + f_code.args
- func = new.function(f_code.to_code(), f.func_globals, f.func_name,
- f.func_defaults, f.func_closure)
-
- return PersistentLocalsFunction(func)
-
-
-_docpostfix = """
-
-This function has been decorated with the 'persistent_locals'
-decorator. You can access the dictionary of the variables in the inner
-scope of the function via the 'locals' attribute.
-
-For more information about the original function, query the self._func
-attribute.
-"""
-
-class PersistentLocalsFunction(object):
- """Wrapper class for the 'persistent_locals' decorator.
-
- Refer to the docstring of instances for help about the wrapped
- function.
- """
+# persistent_locals2 has been co-authored with Andrea Maffezzoli
+class persistent_locals2(object):
def __init__(self, func):
self._locals = {}
-
- # make function an instance method
- self._func = new.instancemethod(func, self, PersistentLocalsFunction)
-
- # create nice-looking doc string for the class
- signature = inspect.getargspec(func)
- signature[0].pop(0) # remove 'self' argument
- signature = inspect.formatargspec(*signature)
-
- docprefix = func.func_name + signature
-
- default_doc = '<no docstring>'
- self.__doc__ = (docprefix + '\n\n' + (func.__doc__ or default_doc)
- + _docpostfix)
-
+ self.func = func
+
def __call__(self, *args, **kwargs):
- return self._func(*args, **kwargs)
-
+ def tracer(frame, event, arg):
+ if event=='return':
+ self._locals = frame.f_locals.copy()
+
+ # tracer is activated on next call, return or exception
+ sys.setprofile(tracer)
+ try:
+ # trace the function call
+ res = self.func(*args, **kwargs)
+ finally:
+ # disable tracer and replace with old one
+ sys.setprofile(None)
+ return res
+
+ def clear_locals(self):
+ self._locals = {}
+
@property
def locals(self):
return self._locals