-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdeco.py
140 lines (109 loc) · 4.32 KB
/
deco.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import new
import byteplay as bp
import inspect
import sys
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
function attribute 'locals'.
This decorator wraps the function in a callable object, and
modifies its bytecode by adding an external try...finally
statement as follows:
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.
"""
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)
def __call__(self, *args, **kwargs):
return self._func(*args, **kwargs)
@property
def locals(self):
return self._locals
# persistent_locals2 has been co-authored with Andrea Maffezzoli
class persistent_locals2(object):
"""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
function attribute 'locals'.
This decorator does not play nice with profilers, and will cause
them to not be able to assign execution time to functions.
"""
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