invocation-tree 0.0.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# This file is part of invocation_tree.
|
|
2
|
+
# Copyright (c) 2023, Bas Terwijn.
|
|
3
|
+
# SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
|
|
5
|
+
from graphviz import Digraph
|
|
6
|
+
import html
|
|
7
|
+
import sys
|
|
8
|
+
import difflib
|
|
9
|
+
|
|
10
|
+
__version__ = "0.0.15"
|
|
11
|
+
__author__ = 'Bas Terwijn'
|
|
12
|
+
|
|
13
|
+
def highlight_diff(str1, str2):
|
|
14
|
+
matcher = difflib.SequenceMatcher(None, str1, str2)
|
|
15
|
+
result = []
|
|
16
|
+
is_highlighted = False
|
|
17
|
+
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
|
18
|
+
if tag == 'replace':
|
|
19
|
+
result.append(f'<B>{str2[j1:j2]}​</B>​')
|
|
20
|
+
is_highlighted = True
|
|
21
|
+
elif tag == 'delete':
|
|
22
|
+
result.append(f'<FONT COLOR="#aaaaaa"><I>{str1[i1:i2]}​</I></FONT>​')
|
|
23
|
+
is_highlighted = True
|
|
24
|
+
elif tag == 'insert':
|
|
25
|
+
result.append(f'<B>{str2[j1:j2]}​</B>​')
|
|
26
|
+
is_highlighted = True
|
|
27
|
+
elif tag == 'equal':
|
|
28
|
+
result.append(str2[j1:j2])
|
|
29
|
+
diff = ''.join(result)
|
|
30
|
+
return diff, is_highlighted
|
|
31
|
+
|
|
32
|
+
def get_class_function_name(frame):
|
|
33
|
+
class_name = ''
|
|
34
|
+
if 'self' in frame.f_locals:
|
|
35
|
+
class_name = frame.f_locals['self'].__class__.__name__ + '.'
|
|
36
|
+
function_name = class_name+frame.f_code.co_name
|
|
37
|
+
return function_name
|
|
38
|
+
|
|
39
|
+
def filter_variables(var, val):
|
|
40
|
+
if callable(val):
|
|
41
|
+
return False
|
|
42
|
+
if isinstance(val, (type, type(object), type(__import__('os')))):
|
|
43
|
+
return False
|
|
44
|
+
if var.startswith('__'):
|
|
45
|
+
return False
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
class Tree_Node:
|
|
49
|
+
|
|
50
|
+
def __init__(self, node_id, frame, return_value):
|
|
51
|
+
self.node_id = node_id
|
|
52
|
+
self.frame = frame
|
|
53
|
+
self.return_value = return_value
|
|
54
|
+
self.is_returned = False
|
|
55
|
+
self.strings = {}
|
|
56
|
+
|
|
57
|
+
def __repr__(self):
|
|
58
|
+
return f'node_id:{self.node_id} frame:{self.frame} return_value:{self.return_value}'
|
|
59
|
+
|
|
60
|
+
class Invocation_Tree:
|
|
61
|
+
|
|
62
|
+
def __init__(self,
|
|
63
|
+
filename='tree.pdf',
|
|
64
|
+
show=True,
|
|
65
|
+
block=True,
|
|
66
|
+
src_loc=True,
|
|
67
|
+
each_line=False,
|
|
68
|
+
gifcount=-1,
|
|
69
|
+
max_string_len=150,
|
|
70
|
+
indent=' ',
|
|
71
|
+
color_paused = '#ccffcc',
|
|
72
|
+
color_active = '#ffffff',
|
|
73
|
+
color_returned = '#ffcccc',
|
|
74
|
+
to_string=None,
|
|
75
|
+
hide=None,
|
|
76
|
+
cleanup=True,
|
|
77
|
+
quiet=True):
|
|
78
|
+
# --- config
|
|
79
|
+
self.filename = filename
|
|
80
|
+
self.prev_filename = None
|
|
81
|
+
self.show = show
|
|
82
|
+
self.block = block
|
|
83
|
+
self.src_loc = src_loc
|
|
84
|
+
self.max_string_len = max_string_len
|
|
85
|
+
self.gifcount = gifcount
|
|
86
|
+
self.indent = indent
|
|
87
|
+
self.color_paused = color_paused
|
|
88
|
+
self.color_active = color_active
|
|
89
|
+
self.color_returned = color_returned
|
|
90
|
+
self.each_line = each_line
|
|
91
|
+
self.to_string = {}
|
|
92
|
+
if not to_string is None:
|
|
93
|
+
self.to_string = to_string
|
|
94
|
+
self.hide = set()
|
|
95
|
+
if not hide is None:
|
|
96
|
+
self.hide = hide
|
|
97
|
+
self.cleanup = cleanup
|
|
98
|
+
self.quiet = quiet
|
|
99
|
+
# --- core
|
|
100
|
+
self.stack = []
|
|
101
|
+
self.returned = []
|
|
102
|
+
self.prev_returned = []
|
|
103
|
+
self.node_id = 0
|
|
104
|
+
self.node_id_to_table = {}
|
|
105
|
+
self.edges = []
|
|
106
|
+
self.is_highlighted = False
|
|
107
|
+
self.ignore_calls = {'Invocation_Tree.__exit__', 'Invocation_Tree.stop_trace'}
|
|
108
|
+
|
|
109
|
+
def __repr__(self):
|
|
110
|
+
return f'Invocation_Tree(filename={repr(self.filename)}, show={self.show}, block={self.block}, each_line={self.each_line}, gifcount={self.gifcount})'
|
|
111
|
+
|
|
112
|
+
def __call__(self, fun, *args, **kwargs):
|
|
113
|
+
try:
|
|
114
|
+
sys.settrace(self.trace_calls)
|
|
115
|
+
result = fun(*args, **kwargs)
|
|
116
|
+
finally:
|
|
117
|
+
sys.settrace(None)
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
def value_to_string(self, key, value, use_repr=False):
|
|
121
|
+
try:
|
|
122
|
+
if id(value) in self.to_string:
|
|
123
|
+
val_str = self.to_string[id(value)](value)
|
|
124
|
+
elif key in self.to_string:
|
|
125
|
+
val_str = self.to_string[key](value)
|
|
126
|
+
elif type(value) in self.to_string:
|
|
127
|
+
val_str = self.to_string[type(value)](value)
|
|
128
|
+
else:
|
|
129
|
+
val_str = repr(value) if use_repr else str(value)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
val_str = '<not-string-convertable>'
|
|
132
|
+
if len(val_str) > self.max_string_len:
|
|
133
|
+
val_str = '...'+val_str[-self.max_string_len:]
|
|
134
|
+
return html.escape(val_str)
|
|
135
|
+
|
|
136
|
+
def get_hightlighted_content(self, tree_node, key, value, use_old_content=False, use_repr=False):
|
|
137
|
+
if use_old_content:
|
|
138
|
+
return tree_node.strings[key]
|
|
139
|
+
is_highlighted = False
|
|
140
|
+
content = self.value_to_string(key, value, use_repr=use_repr)
|
|
141
|
+
if key in tree_node.strings:
|
|
142
|
+
use_old_content = tree_node.strings[key]
|
|
143
|
+
hightlighted_content, is_highlighted = highlight_diff(use_old_content, content)
|
|
144
|
+
else:
|
|
145
|
+
if len(content.strip())>0: # fixes graphviz error on empty <B></B> tag
|
|
146
|
+
hightlighted_content = '<B>'+content+'</B>'
|
|
147
|
+
is_highlighted = True
|
|
148
|
+
else:
|
|
149
|
+
hightlighted_content = content
|
|
150
|
+
tree_node.strings[key] = content
|
|
151
|
+
self.is_highlighted |= is_highlighted
|
|
152
|
+
return hightlighted_content
|
|
153
|
+
|
|
154
|
+
def build_html_table(self, tree_node, active=False, is_returned=None, use_old_content=False):
|
|
155
|
+
if is_returned is None:
|
|
156
|
+
is_returned = tree_node.is_returned
|
|
157
|
+
else:
|
|
158
|
+
tree_node.is_returned = is_returned
|
|
159
|
+
return_value = tree_node.return_value
|
|
160
|
+
border = 1
|
|
161
|
+
color = self.color_paused
|
|
162
|
+
if active:
|
|
163
|
+
color = self.color_active
|
|
164
|
+
border = 3
|
|
165
|
+
if is_returned:
|
|
166
|
+
color = self.color_returned
|
|
167
|
+
table = f'<\n<TABLE BORDER="{str(border)}" CELLBORDER="0" CELLSPACING="0" BGCOLOR="{color}">\n <TR>'
|
|
168
|
+
class_fun_name = get_class_function_name(tree_node.frame)
|
|
169
|
+
local_vars = tree_node.frame.f_locals
|
|
170
|
+
hightlighted_content = self.get_hightlighted_content(tree_node, class_fun_name, class_fun_name, use_old_content)
|
|
171
|
+
table += '<TD ALIGN="left">'+ '➤'+ hightlighted_content +'</TD>'
|
|
172
|
+
for var,val in local_vars.items():
|
|
173
|
+
var_name = class_fun_name+'..'+var
|
|
174
|
+
val_name = class_fun_name+'.'+var
|
|
175
|
+
if filter_variables(var,val) and not val_name in self.hide:
|
|
176
|
+
table += '</TR>\n <TR>'
|
|
177
|
+
hightlighted_var = self.get_hightlighted_content(tree_node, var_name, var, use_old_content)
|
|
178
|
+
hightlighted_val = self.get_hightlighted_content(tree_node, val_name, val, use_old_content, use_repr=True)
|
|
179
|
+
hightlighted_content = self.indent + hightlighted_var + ': ' + hightlighted_val
|
|
180
|
+
table += '<TD ALIGN="left">'+ hightlighted_content +'</TD>'
|
|
181
|
+
if is_returned:
|
|
182
|
+
return_name = class_fun_name+'.return'
|
|
183
|
+
if not return_name in self.hide:
|
|
184
|
+
table += '</TR>\n <TR>'
|
|
185
|
+
hightlighted_content = self.get_hightlighted_content(tree_node, return_name, return_value, use_old_content, use_repr=True)
|
|
186
|
+
table += '<TD ALIGN="left">'+ 'return ' + hightlighted_content +'</TD>'
|
|
187
|
+
table += '</TR>\n</TABLE>>'
|
|
188
|
+
return table
|
|
189
|
+
|
|
190
|
+
def update_node(self, tree_node, active=False, returned=None, use_old_content=False):
|
|
191
|
+
table = self.build_html_table(tree_node, active, returned, use_old_content=use_old_content)
|
|
192
|
+
self.node_id_to_table[str(tree_node.node_id)] = table
|
|
193
|
+
|
|
194
|
+
def add_edge(self, tree_node1, tree_node2):
|
|
195
|
+
self.edges.append((str(tree_node1.node_id), str(tree_node2.node_id)))
|
|
196
|
+
|
|
197
|
+
def get_output_filename(self):
|
|
198
|
+
if self.gifcount >= 0:
|
|
199
|
+
splits = self.filename.split('.')
|
|
200
|
+
if len(splits)>1:
|
|
201
|
+
splits[-2]+=str(self.gifcount)
|
|
202
|
+
self.gifcount += 1
|
|
203
|
+
return '.'.join(splits)
|
|
204
|
+
return self.filename
|
|
205
|
+
|
|
206
|
+
def create_graph(self):
|
|
207
|
+
graphviz_graph_attr = {}
|
|
208
|
+
graphviz_node_attr = {'shape':'plaintext'}
|
|
209
|
+
graphviz_edge_attr = {}
|
|
210
|
+
graph = Digraph('invocation_tree',
|
|
211
|
+
graph_attr=graphviz_graph_attr,
|
|
212
|
+
node_attr=graphviz_node_attr,
|
|
213
|
+
edge_attr=graphviz_edge_attr)
|
|
214
|
+
for node in self.prev_returned:
|
|
215
|
+
self.update_node(node, use_old_content=True)
|
|
216
|
+
self.prev_returned = []
|
|
217
|
+
for node in self.returned:
|
|
218
|
+
self.update_node(node, returned=True)
|
|
219
|
+
self.prev_returned.append(node)
|
|
220
|
+
self.returned = []
|
|
221
|
+
for node in self.stack:
|
|
222
|
+
self.update_node(node, active=(node is self.stack[-1]))
|
|
223
|
+
for nid, table in self.node_id_to_table.items():
|
|
224
|
+
graph.node(nid, label=table)
|
|
225
|
+
for nid1, nid2 in self.edges:
|
|
226
|
+
graph.edge(nid1, nid2)
|
|
227
|
+
return graph
|
|
228
|
+
|
|
229
|
+
def render_graph(self, graph):
|
|
230
|
+
view = (self.filename!=self.prev_filename) and self.show
|
|
231
|
+
graph.render(outfile=self.get_output_filename(), view=view, cleanup=self.cleanup, quiet=self.quiet)
|
|
232
|
+
self.prev_filename = self.filename
|
|
233
|
+
|
|
234
|
+
def output_graph(self, frame, event):
|
|
235
|
+
if self.block or self.gifcount >= 0:
|
|
236
|
+
self.is_highlighted = False
|
|
237
|
+
graph = self.create_graph()
|
|
238
|
+
if self.is_highlighted:
|
|
239
|
+
self.render_graph(graph)
|
|
240
|
+
if self.block:
|
|
241
|
+
if self.src_loc:
|
|
242
|
+
filename = frame.f_code.co_filename
|
|
243
|
+
line_nr = frame.f_lineno
|
|
244
|
+
print(f'{event.capitalize()} at {filename}:{line_nr}', end='. ')
|
|
245
|
+
input('Press <Enter> to continue...')
|
|
246
|
+
else:
|
|
247
|
+
graph = self.create_graph()
|
|
248
|
+
self.render_graph(graph)
|
|
249
|
+
|
|
250
|
+
def trace_calls(self, frame, event, arg):
|
|
251
|
+
class_fun_name = get_class_function_name(frame)
|
|
252
|
+
if not class_fun_name in self.ignore_calls:
|
|
253
|
+
if event == 'call':
|
|
254
|
+
self.stack.append(Tree_Node(self.node_id, frame, None))
|
|
255
|
+
self.node_id += 1
|
|
256
|
+
if len(self.stack)>1:
|
|
257
|
+
self.add_edge(self.stack[-2], self.stack[-1])
|
|
258
|
+
self.output_graph(frame, event)
|
|
259
|
+
elif event == 'return':
|
|
260
|
+
self.stack[-1].return_value = arg
|
|
261
|
+
self.returned.append(self.stack.pop())
|
|
262
|
+
self.output_graph(frame, event)
|
|
263
|
+
elif event == 'line' and self.each_line:
|
|
264
|
+
self.output_graph(frame, event)
|
|
265
|
+
return self.trace_calls
|
|
266
|
+
|
|
267
|
+
def blocking(filename='tree.pdf'):
|
|
268
|
+
return Invocation_Tree(filename=filename)
|
|
269
|
+
|
|
270
|
+
def blocking_each_change(filename='tree.pdf'):
|
|
271
|
+
return Invocation_Tree(filename=filename, each_line=True)
|
|
272
|
+
|
|
273
|
+
def debugger(filename='tree.pdf'):
|
|
274
|
+
return Invocation_Tree(filename=filename, show=False, block=False, each_line=True)
|
|
275
|
+
|
|
276
|
+
def gif(filename='tree.png'):
|
|
277
|
+
return Invocation_Tree(filename=filename, show=False, block=False, gifcount=0)
|
|
278
|
+
|
|
279
|
+
def gif_each_change(filename='tree.png'):
|
|
280
|
+
return Invocation_Tree(filename=filename, show=False, block=False, gifcount=0, each_line=True)
|
|
281
|
+
|
|
282
|
+
def non_blocking(filename='tree.pdf'):
|
|
283
|
+
return Invocation_Tree(filename=filename, block=False)
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: invocation_tree
|
|
3
|
+
Version: 0.0.15
|
|
4
|
+
Summary: Generates an invocation tree of functions calls.
|
|
5
|
+
Author-email: Bas Terwijn <bterwijn@gmail.com>
|
|
6
|
+
License-Expression: BSD-2-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/bterwijn/invocation-tree
|
|
8
|
+
Project-URL: Repository, https://github.com/bterwijn/invocation-tree.git
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Education
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Education
|
|
14
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
15
|
+
Requires-Python: >=3.7
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE.txt
|
|
18
|
+
Requires-Dist: graphviz
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# Installation #
|
|
22
|
+
Install (or upgrade) `invocation_tree` using pip:
|
|
23
|
+
```
|
|
24
|
+
pip install --upgrade invocation_tree
|
|
25
|
+
```
|
|
26
|
+
Additionally [Graphviz](https://graphviz.org/download/) needs to be installed.
|
|
27
|
+
|
|
28
|
+
# Invocation Tree #
|
|
29
|
+
The [invocation_tree](https://pypi.org/project/invocation-tree/) package is designed to help with **program understanding and debugging** by visualizing the **tree of function invocations** that occur during program execution. Here’s a simple example of how it works, we start with `a = 1` and compute:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
(a - 3 + 9) * 6
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import invocation_tree as ivt
|
|
37
|
+
|
|
38
|
+
def main():
|
|
39
|
+
a = 1
|
|
40
|
+
a = expression(a)
|
|
41
|
+
return multiply(a, 6)
|
|
42
|
+
|
|
43
|
+
def expression(a):
|
|
44
|
+
a = subtract(a, 3)
|
|
45
|
+
return add(a, 9)
|
|
46
|
+
|
|
47
|
+
def subtract(a, b):
|
|
48
|
+
return a - b
|
|
49
|
+
|
|
50
|
+
def add(a, b):
|
|
51
|
+
return a + b
|
|
52
|
+
|
|
53
|
+
def multiply(a, b):
|
|
54
|
+
return a * b
|
|
55
|
+
|
|
56
|
+
tree = ivt.blocking()
|
|
57
|
+
print( tree(main) ) # show invocation tree starting at main
|
|
58
|
+
```
|
|
59
|
+
Running the program and pressing <Enter> a number of times results in:
|
|
60
|
+

|
|
61
|
+
```
|
|
62
|
+
42
|
|
63
|
+
```
|
|
64
|
+
Each node in the tree represents a function call, and the node's color indicates its state:
|
|
65
|
+
|
|
66
|
+
- White: The function is currently being executed (it is at the top of the call stack).
|
|
67
|
+
- Green: The function is paused and will resume execution later (it is lower down on the call stack).
|
|
68
|
+
- Red: The function has completed execution and returned (it has been removed from the call stack).
|
|
69
|
+
|
|
70
|
+
For every function, the package displays its **local variables** and **return value**. Changes to these values over time are highlighted using bold text and gray shading to make them easy to track.
|
|
71
|
+
|
|
72
|
+
# Chapters #
|
|
73
|
+
|
|
74
|
+
[Comprehensions](#Comprehensions)
|
|
75
|
+
|
|
76
|
+
[Debugger](#Debugger)
|
|
77
|
+
|
|
78
|
+
[Recursion](#Recursion)
|
|
79
|
+
|
|
80
|
+
[Configuration](#Configuration)
|
|
81
|
+
|
|
82
|
+
[Troubleshooting](#Troubleshooting)
|
|
83
|
+
|
|
84
|
+
# Author #
|
|
85
|
+
Bas Terwijn
|
|
86
|
+
|
|
87
|
+
# Inspiration #
|
|
88
|
+
Inspired by [rcviz](https://github.com/carlsborg/rcviz).
|
|
89
|
+
|
|
90
|
+
# Supported by #
|
|
91
|
+
<img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/uva.png" alt="University of Amsterdam" width="600">
|
|
92
|
+
|
|
93
|
+
___
|
|
94
|
+
___
|
|
95
|
+
|
|
96
|
+
# Comprehensions #
|
|
97
|
+
In this more interesting example we compute which students pass a course by using list and dictionary comprehensions.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import invocation_tree as ivt
|
|
101
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
102
|
+
|
|
103
|
+
def main():
|
|
104
|
+
students = {'Ann':[7.5, 8.0],
|
|
105
|
+
'Bob':[4.5, 6.0],
|
|
106
|
+
'Coy':[7.5, 6.0]}
|
|
107
|
+
averages = {student:compute_average(grades)
|
|
108
|
+
for student, grades in students.items()}
|
|
109
|
+
passing = passing_students(averages)
|
|
110
|
+
print(passing)
|
|
111
|
+
|
|
112
|
+
def compute_average(grades):
|
|
113
|
+
average = sum(grades)/len(grades)
|
|
114
|
+
return half_up_round(average, 1)
|
|
115
|
+
|
|
116
|
+
def half_up_round(value, digits=0):
|
|
117
|
+
""" High-precision half-up rounding of 'value' to a specified number of 'digits'. """
|
|
118
|
+
return float(Decimal(str(value)).quantize(Decimal(f"1e-{digits}"),
|
|
119
|
+
rounding=ROUND_HALF_UP))
|
|
120
|
+
|
|
121
|
+
def passing_students(averages):
|
|
122
|
+
return [student
|
|
123
|
+
for student, average in averages.items()
|
|
124
|
+
if average >= 5.5]
|
|
125
|
+
|
|
126
|
+
if __name__ == '__main__':
|
|
127
|
+
tree = ivt.blocking()
|
|
128
|
+
tree(main)
|
|
129
|
+
```
|
|
130
|
+

|
|
131
|
+
```
|
|
132
|
+
['Ann', 'Coy']
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Blocking ##
|
|
136
|
+
The program blocks execution at every function call and return statement, printing the current location in the source code. Press the <Enter> key to continue execution. To block at every line of the program (like in a debugger tool) and only where a change of value occured, use instead:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
tree = ivt.blocking_each_change()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
# Debugger #
|
|
143
|
+
To visualize the invocation tree in a debugger tool, such as the integrated debugger in Visual Studio Code, use instead:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
tree = ivt.debugger()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
and open the 'tree.pdf' file manually.
|
|
150
|
+

|
|
151
|
+
|
|
152
|
+
# Recursion #
|
|
153
|
+
An invocation tree is particularly helpful to better understand recursion. A simple `factorial()` example:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import invocation_tree as ivt
|
|
157
|
+
|
|
158
|
+
def factorial(n):
|
|
159
|
+
if n <= 1:
|
|
160
|
+
return 1
|
|
161
|
+
prev_result = factorial(n - 1)
|
|
162
|
+
return n * prev_result
|
|
163
|
+
|
|
164
|
+
tree = ivt.blocking()
|
|
165
|
+
print( tree(factorial, 4) ) # show invocation tree of calling factorial(4)
|
|
166
|
+
```
|
|
167
|
+

|
|
168
|
+
```
|
|
169
|
+
24
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Permutations ##
|
|
173
|
+
This `permutations()` example shows the depth-first nature of recursive execution:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
import invocation_tree as ivt
|
|
177
|
+
|
|
178
|
+
def permutations(elements, perm, n):
|
|
179
|
+
if n==0:
|
|
180
|
+
return [perm]
|
|
181
|
+
all_perms = []
|
|
182
|
+
for element in elements:
|
|
183
|
+
all_perms.extend(permutations(elements, perm + element, n-1))
|
|
184
|
+
return all_perms
|
|
185
|
+
|
|
186
|
+
tree = ivt.blocking()
|
|
187
|
+
result = tree(permutations, ['L','R'], '', 2)
|
|
188
|
+
print(result) # all permutations of going Left or Right of length 2
|
|
189
|
+
```
|
|
190
|
+

|
|
191
|
+
```
|
|
192
|
+
['LL', 'LR', 'RL', 'RR']
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Hide Variables ##
|
|
196
|
+
In an educational context it can be useful to hide certian variables to avoid unnecessary complexity. This can for example be done with:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
tree = ivt.blocking()
|
|
200
|
+
tree.hide.add('permutations.elements')
|
|
201
|
+
tree.hide.add('permutations.element')
|
|
202
|
+
tree.hide.add('permutations.all_perms')
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
# Configuration #
|
|
206
|
+
These invocation_tree configurations are available for an `Invocation_Tree` objects:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
tree = ivt.Invocation_Tree()
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- **tree.filename** : str
|
|
213
|
+
- filename to save the tree to, defaults to 'tree.pdf'
|
|
214
|
+
- **tree.show** : bool
|
|
215
|
+
- if `True` the default application is open to view 'tree.filename'
|
|
216
|
+
- **tree.block** : bool
|
|
217
|
+
- if `True` program execution is blocked after the tree is saved
|
|
218
|
+
- **tree.src_loc** : bool
|
|
219
|
+
- if `True` the source location is printed when blocking
|
|
220
|
+
- **tree.each_line** : bool
|
|
221
|
+
- if `True` each line of the program is stepped through
|
|
222
|
+
- **tree.max_string_len** : int
|
|
223
|
+
- the maximum string length, only the end is shown of longer strings
|
|
224
|
+
- **tree.gifcount** : int
|
|
225
|
+
- if `>=0` the out filename is numbered for animated gif making
|
|
226
|
+
- **tree.indent** : string
|
|
227
|
+
- the string used for identing the local variables
|
|
228
|
+
- **tree.color_active** : string
|
|
229
|
+
- HTML color for active function
|
|
230
|
+
- **tree.color_paused*** : string
|
|
231
|
+
- HTML color for paused functions
|
|
232
|
+
- **tree.color_returned***: string
|
|
233
|
+
- HTML color for returned functions
|
|
234
|
+
- **tree.hide** : set()
|
|
235
|
+
- set of all variables names that are not shown in the tree
|
|
236
|
+
- **tree.to_string** : dict[str, fun]
|
|
237
|
+
- mapping from type/name to a to_string() function for custom printing of values
|
|
238
|
+
|
|
239
|
+
For convenience we provide these functions to set common configurations:
|
|
240
|
+
|
|
241
|
+
- **ivt.blocking(filename)**, blocks on function call and return
|
|
242
|
+
- **ivt.blocking_each_change(filename)**, blocks on each change of value
|
|
243
|
+
- **ivt.debugger(filename)**, non-blocking for use in debugger tool (open <filename> manually)
|
|
244
|
+
- **ivt.gif(filename)**, generates many output files on function call and return for gif creation
|
|
245
|
+
- **ivt.gif_each_change(filename)**, generates many output files on each change of value for gif creation
|
|
246
|
+
- **ivt.non_blocking(filename)**, non-blocking on each function call and return
|
|
247
|
+
|
|
248
|
+
# Troubleshooting #
|
|
249
|
+
- Adobe Acrobat Reader [doesn't refresh a PDF file](https://community.adobe.com/t5/acrobat-reader-discussions/reload-refresh-pdfs/td-p/9632292) when it changes on disk and blocks updates which results in an `Could not open 'somefile.pdf' for writing : Permission denied` error. One solution is to install a PDF reader that does refresh ([SumatraPDF](https://www.sumatrapdfreader.org/), [Okular](https://okular.kde.org/), ...) and set it as the default PDF reader. Another solution is to `render()` the graph to a different output format and to open it manually.
|
|
250
|
+
|
|
251
|
+
## Memory_Graph Package ##
|
|
252
|
+
The [invocation_tree](https://pypi.org/project/invocation-tree/) package visualizes function calls at different moments in time. If instead you want a detailed visualization of your data at the current time, check out the [memory_graph](https://pypi.org/project/memory-graph/) package.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
invocation_tree/__init__.py,sha256=mOh8VMlN_2QtFXPO3Sf0teB-3gzG5TDzIHPhKZOrR6E,11222
|
|
2
|
+
invocation_tree-0.0.15.dist-info/licenses/LICENSE.txt,sha256=lhBfhX4yJut_-ahPAsH87Xhk-01Df17Std-X1Xy6rhU,1314
|
|
3
|
+
invocation_tree-0.0.15.dist-info/METADATA,sha256=or2WV3il5CDUPBmXGJzs8wusp1YQUv4Bphl8gBru6pw,8971
|
|
4
|
+
invocation_tree-0.0.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
+
invocation_tree-0.0.15.dist-info/top_level.txt,sha256=-UiQipEd5_8mqbNW12sUtrMfxlTqV0HV0T4WFCeeD_I,16
|
|
6
|
+
invocation_tree-0.0.15.dist-info/RECORD,,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017, pyexample
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
invocation_tree
|