memory-graph 0.3.9__tar.gz → 0.3.10__tar.gz
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.
- {memory_graph-0.3.9/memory_graph.egg-info → memory_graph-0.3.10}/PKG-INFO +36 -13
- {memory_graph-0.3.9 → memory_graph-0.3.10}/README.md +35 -12
- memory_graph-0.3.10/images/add_one.png +0 -0
- memory_graph-0.3.10/images/bin_tree.png +0 -0
- memory_graph-0.3.10/images/copies.png +0 -0
- memory_graph-0.3.10/images/debugging.gif +0 -0
- memory_graph-0.3.10/images/highlight.png +0 -0
- memory_graph-0.3.10/images/ipython.png +0 -0
- memory_graph-0.3.10/images/power_set.gif +0 -0
- memory_graph-0.3.10/images/pyodide.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/__init__.py +70 -44
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/config.py +1 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/config_default.py +4 -1
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/memory_to_nodes.py +1 -2
- {memory_graph-0.3.9 → memory_graph-0.3.10/memory_graph.egg-info}/PKG-INFO +36 -13
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph.egg-info/SOURCES.txt +4 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/setup.py +1 -1
- memory_graph-0.3.10/src/auto_memory_graph.py +18 -0
- memory_graph-0.3.10/src/pyodide.html +179 -0
- memory_graph-0.3.9/images/add_one.png +0 -0
- memory_graph-0.3.9/images/bin_tree.png +0 -0
- memory_graph-0.3.9/images/copies.png +0 -0
- memory_graph-0.3.9/images/debugging.gif +0 -0
- memory_graph-0.3.9/images/highlight.png +0 -0
- memory_graph-0.3.9/images/power_set.gif +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/LICENSE.txt +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/MANIFEST.in +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/TODO.txt +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/add_one.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_base.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_dir.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_fail.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_key_value.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_linear.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_table.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/bin_tree.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/copies.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/copy_method.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/copy_method.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/create_gif.sh +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/create_images.sh +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/debugging.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/extension_numpy.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/extension_numpy.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/extension_pandas.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/extension_pandas.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/factorial.gif +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/factorial.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/hash_set.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/hash_set.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/highlight.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/immutable.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/immutable1.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/immutable2.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/jupyter_example.ipynb +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/jupyter_example.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/linked_list.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/linked_list.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/many_types.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/many_types.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/mutable.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/mutable1.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/mutable2.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/power_set.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/images/uva.png +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/install.txt +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/config_helpers.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/extension_numpy.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/extension_pandas.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/html_table.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/list_view.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/node_base.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/node_key_value.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/node_linear.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/node_table.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/sequence.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/slicer.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/slices.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/slices_iterator.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/slices_table_iterator.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_memory_graph.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_memory_to_nodes.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_sequence.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_slicer.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_slices.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_slices_iterator.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/utils.py +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph.egg-info/dependency_links.txt +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph.egg-info/requires.txt +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph.egg-info/top_level.txt +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/setup.cfg +0 -0
- {memory_graph-0.3.9 → memory_graph-0.3.10}/uml/memory_graph.uxf +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: memory_graph
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
4
4
|
Summary: Draws a graph of your data to analyze its structure.
|
|
5
5
|
Home-page: https://github.com/bterwijn/memory_graph
|
|
6
6
|
Author: Bas Terwijn
|
|
@@ -91,7 +91,7 @@ mg.render(data, "my_graph.pdf")
|
|
|
91
91
|
mg.render(data, "my_graph.svg")
|
|
92
92
|
mg.render(data, "my_graph.png")
|
|
93
93
|
mg.render(data, "my_graph.gv") # Graphviz DOT file
|
|
94
|
-
mg.render(data) # renders to 'mg.render_filename'
|
|
94
|
+
mg.render(data) # renders to 'mg.render_filename' with default value: 'memory_graph.pdf'
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
# Chapters #
|
|
@@ -112,6 +112,10 @@ mg.render(data) # renders to 'mg.render_filename' (default value: 'memory_graph.
|
|
|
112
112
|
|
|
113
113
|
[Jupyter Notebook](#jupyter-notebook)
|
|
114
114
|
|
|
115
|
+
[ipython](#ipython)
|
|
116
|
+
|
|
117
|
+
[In the Browser](#in-the-browser)
|
|
118
|
+
|
|
115
119
|
[Troubleshooting](#troubleshooting)
|
|
116
120
|
|
|
117
121
|
|
|
@@ -152,7 +156,7 @@ mg.render(locals(), 'immutable2.png')
|
|
|
152
156
|
|
|
153
157
|
|
|
154
158
|
### Mutable Type ###
|
|
155
|
-
With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `b`
|
|
159
|
+
With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `a` and `b` are independent.
|
|
156
160
|
|
|
157
161
|
```python
|
|
158
162
|
import memory_graph as mg
|
|
@@ -167,7 +171,7 @@ mg.render(locals(), 'mutable2.png')
|
|
|
167
171
|
|:-----------------------------------------------------------:|:-------------------------------------------------------------:|
|
|
168
172
|
| mutable1.png | mutable2.png |
|
|
169
173
|
|
|
170
|
-
One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type
|
|
174
|
+
One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type can be large, making it inefficient to copy each time we change it. Immutable values generally don't need to change as much, or are small which makes copying less of a concern.
|
|
171
175
|
|
|
172
176
|
### Copying ###
|
|
173
177
|
Python offers three different "copy" options that we will demonstrate using a nested list:
|
|
@@ -220,7 +224,7 @@ mg.show(locals())
|
|
|
220
224
|
|
|
221
225
|
|
|
222
226
|
## Call Stack ##
|
|
223
|
-
The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters
|
|
227
|
+
The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters `a`, `b`, and `c`.
|
|
224
228
|
|
|
225
229
|
```python
|
|
226
230
|
import memory_graph as mg
|
|
@@ -254,9 +258,15 @@ It is often helpful to temporarily block program execution to inspect the graph.
|
|
|
254
258
|
mg.block(fun, arg1, arg2, ...)
|
|
255
259
|
```
|
|
256
260
|
|
|
257
|
-
This function
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
This function:
|
|
262
|
+
* first executes `fun(arg1, arg2, ...)`
|
|
263
|
+
* then prints the current source location in the program
|
|
264
|
+
* then blocks execution until the <Enter> key is pressed
|
|
265
|
+
* finally returns the value of the `fun()` call
|
|
266
|
+
|
|
267
|
+
to change it's behavior:
|
|
268
|
+
* Set `mg.block_prints_location = False` to skip printing the source location.
|
|
269
|
+
* Set `mg.press_enter_message = None` to skip printing "Press <Enter> to continue...".
|
|
260
270
|
|
|
261
271
|
### Recursion ###
|
|
262
272
|
The call stack is also helpful to visualize how recursion works. Here we use `mg.block()` to show each step of how recursively ```factorial(3)``` is computed:
|
|
@@ -620,9 +630,9 @@ mg.show(locals())
|
|
|
620
630
|
```
|
|
621
631
|

|
|
622
632
|
|
|
623
|
-
Next figure out what are the attributes you want to graph and choose a Node type, there are four options
|
|
633
|
+
Next figure out what are the attributes you want to graph and choose a Node type, there are four options:
|
|
624
634
|
|
|
625
|
-
### 1 Node_Base ###
|
|
635
|
+
### 1) Node_Base ###
|
|
626
636
|
Node_base is a leaf node (with no children) and shows just a single value.
|
|
627
637
|
```python
|
|
628
638
|
import memory_graph as mg
|
|
@@ -642,7 +652,7 @@ mg.show(locals())
|
|
|
642
652
|
```
|
|
643
653
|

|
|
644
654
|
|
|
645
|
-
### 2 Node_Linear ###
|
|
655
|
+
### 2) Node_Linear ###
|
|
646
656
|
Node_Linear shows all the values in a line like a list.
|
|
647
657
|
```python
|
|
648
658
|
import memory_graph as mg
|
|
@@ -666,7 +676,7 @@ mg.show(locals())
|
|
|
666
676
|
```
|
|
667
677
|

|
|
668
678
|
|
|
669
|
-
### 3 Node_Key_Value ###
|
|
679
|
+
### 3) Node_Key_Value ###
|
|
670
680
|
Node_Key_Value shows key-value pairs like a dictionary. Note the required `items()` call at the end.
|
|
671
681
|
```python
|
|
672
682
|
import memory_graph as mg
|
|
@@ -690,7 +700,7 @@ mg.show(locals())
|
|
|
690
700
|
```
|
|
691
701
|

|
|
692
702
|
|
|
693
|
-
### 4 Node_Table ###
|
|
703
|
+
### 4) Node_Table ###
|
|
694
704
|
Node_Table shows all the values as a table.
|
|
695
705
|
```python
|
|
696
706
|
import memory_graph as mg
|
|
@@ -727,6 +737,19 @@ mg.block(display, mg.create_graph(mg.locals_jupyter()) ) # the same but blocked
|
|
|
727
737
|
See for example [jupyter_example.ipynb](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.ipynb).
|
|
728
738
|

|
|
729
739
|
|
|
740
|
+
## ipython ##
|
|
741
|
+
In ipython `locals()` has additional variables that cause problems in the graph, use `mg.locals_ipython()` to get the local variables with these problematic variables filtered out. Use `mg.get_call_stack_ipython()` to get the whole call stack with these variables filtered out.
|
|
742
|
+
|
|
743
|
+
Additionally install file [auto_memory_graph.py](https://raw.githubusercontent.com/bterwijn/memory_graph/main/sc/auto_memory_graph.py) in the ipython startup directory:
|
|
744
|
+
* Linux/Mac: ~/.ipython/profile_default/startup/
|
|
745
|
+
* Windows: %USERPROFILE%\.ipython\profile_default\startup\
|
|
746
|
+
|
|
747
|
+
Then after starting 'ipython' call function `mg_switch()` to turn on/off the automatic visualization of local variables after each command.
|
|
748
|
+

|
|
749
|
+
|
|
750
|
+
## In the Browser ##
|
|
751
|
+
We can run memory_graph in the browser: <a href="https://bterwijn.github.io/memory_graph/src/pyodide.html" target="_blank">Pyodide Example</a>
|
|
752
|
+

|
|
730
753
|
|
|
731
754
|
## Troubleshooting ##
|
|
732
755
|
|
|
@@ -72,7 +72,7 @@ mg.render(data, "my_graph.pdf")
|
|
|
72
72
|
mg.render(data, "my_graph.svg")
|
|
73
73
|
mg.render(data, "my_graph.png")
|
|
74
74
|
mg.render(data, "my_graph.gv") # Graphviz DOT file
|
|
75
|
-
mg.render(data) # renders to 'mg.render_filename'
|
|
75
|
+
mg.render(data) # renders to 'mg.render_filename' with default value: 'memory_graph.pdf'
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
# Chapters #
|
|
@@ -93,6 +93,10 @@ mg.render(data) # renders to 'mg.render_filename' (default value: 'memory_graph.
|
|
|
93
93
|
|
|
94
94
|
[Jupyter Notebook](#jupyter-notebook)
|
|
95
95
|
|
|
96
|
+
[ipython](#ipython)
|
|
97
|
+
|
|
98
|
+
[In the Browser](#in-the-browser)
|
|
99
|
+
|
|
96
100
|
[Troubleshooting](#troubleshooting)
|
|
97
101
|
|
|
98
102
|
|
|
@@ -133,7 +137,7 @@ mg.render(locals(), 'immutable2.png')
|
|
|
133
137
|
|
|
134
138
|
|
|
135
139
|
### Mutable Type ###
|
|
136
|
-
With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `b`
|
|
140
|
+
With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `a` and `b` are independent.
|
|
137
141
|
|
|
138
142
|
```python
|
|
139
143
|
import memory_graph as mg
|
|
@@ -148,7 +152,7 @@ mg.render(locals(), 'mutable2.png')
|
|
|
148
152
|
|:-----------------------------------------------------------:|:-------------------------------------------------------------:|
|
|
149
153
|
| mutable1.png | mutable2.png |
|
|
150
154
|
|
|
151
|
-
One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type
|
|
155
|
+
One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type can be large, making it inefficient to copy each time we change it. Immutable values generally don't need to change as much, or are small which makes copying less of a concern.
|
|
152
156
|
|
|
153
157
|
### Copying ###
|
|
154
158
|
Python offers three different "copy" options that we will demonstrate using a nested list:
|
|
@@ -201,7 +205,7 @@ mg.show(locals())
|
|
|
201
205
|
|
|
202
206
|
|
|
203
207
|
## Call Stack ##
|
|
204
|
-
The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters
|
|
208
|
+
The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters `a`, `b`, and `c`.
|
|
205
209
|
|
|
206
210
|
```python
|
|
207
211
|
import memory_graph as mg
|
|
@@ -235,9 +239,15 @@ It is often helpful to temporarily block program execution to inspect the graph.
|
|
|
235
239
|
mg.block(fun, arg1, arg2, ...)
|
|
236
240
|
```
|
|
237
241
|
|
|
238
|
-
This function
|
|
239
|
-
|
|
240
|
-
|
|
242
|
+
This function:
|
|
243
|
+
* first executes `fun(arg1, arg2, ...)`
|
|
244
|
+
* then prints the current source location in the program
|
|
245
|
+
* then blocks execution until the <Enter> key is pressed
|
|
246
|
+
* finally returns the value of the `fun()` call
|
|
247
|
+
|
|
248
|
+
to change it's behavior:
|
|
249
|
+
* Set `mg.block_prints_location = False` to skip printing the source location.
|
|
250
|
+
* Set `mg.press_enter_message = None` to skip printing "Press <Enter> to continue...".
|
|
241
251
|
|
|
242
252
|
### Recursion ###
|
|
243
253
|
The call stack is also helpful to visualize how recursion works. Here we use `mg.block()` to show each step of how recursively ```factorial(3)``` is computed:
|
|
@@ -601,9 +611,9 @@ mg.show(locals())
|
|
|
601
611
|
```
|
|
602
612
|

|
|
603
613
|
|
|
604
|
-
Next figure out what are the attributes you want to graph and choose a Node type, there are four options
|
|
614
|
+
Next figure out what are the attributes you want to graph and choose a Node type, there are four options:
|
|
605
615
|
|
|
606
|
-
### 1 Node_Base ###
|
|
616
|
+
### 1) Node_Base ###
|
|
607
617
|
Node_base is a leaf node (with no children) and shows just a single value.
|
|
608
618
|
```python
|
|
609
619
|
import memory_graph as mg
|
|
@@ -623,7 +633,7 @@ mg.show(locals())
|
|
|
623
633
|
```
|
|
624
634
|

|
|
625
635
|
|
|
626
|
-
### 2 Node_Linear ###
|
|
636
|
+
### 2) Node_Linear ###
|
|
627
637
|
Node_Linear shows all the values in a line like a list.
|
|
628
638
|
```python
|
|
629
639
|
import memory_graph as mg
|
|
@@ -647,7 +657,7 @@ mg.show(locals())
|
|
|
647
657
|
```
|
|
648
658
|

|
|
649
659
|
|
|
650
|
-
### 3 Node_Key_Value ###
|
|
660
|
+
### 3) Node_Key_Value ###
|
|
651
661
|
Node_Key_Value shows key-value pairs like a dictionary. Note the required `items()` call at the end.
|
|
652
662
|
```python
|
|
653
663
|
import memory_graph as mg
|
|
@@ -671,7 +681,7 @@ mg.show(locals())
|
|
|
671
681
|
```
|
|
672
682
|

|
|
673
683
|
|
|
674
|
-
### 4 Node_Table ###
|
|
684
|
+
### 4) Node_Table ###
|
|
675
685
|
Node_Table shows all the values as a table.
|
|
676
686
|
```python
|
|
677
687
|
import memory_graph as mg
|
|
@@ -708,6 +718,19 @@ mg.block(display, mg.create_graph(mg.locals_jupyter()) ) # the same but blocked
|
|
|
708
718
|
See for example [jupyter_example.ipynb](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.ipynb).
|
|
709
719
|

|
|
710
720
|
|
|
721
|
+
## ipython ##
|
|
722
|
+
In ipython `locals()` has additional variables that cause problems in the graph, use `mg.locals_ipython()` to get the local variables with these problematic variables filtered out. Use `mg.get_call_stack_ipython()` to get the whole call stack with these variables filtered out.
|
|
723
|
+
|
|
724
|
+
Additionally install file [auto_memory_graph.py](https://raw.githubusercontent.com/bterwijn/memory_graph/main/sc/auto_memory_graph.py) in the ipython startup directory:
|
|
725
|
+
* Linux/Mac: ~/.ipython/profile_default/startup/
|
|
726
|
+
* Windows: %USERPROFILE%\.ipython\profile_default\startup\
|
|
727
|
+
|
|
728
|
+
Then after starting 'ipython' call function `mg_switch()` to turn on/off the automatic visualization of local variables after each command.
|
|
729
|
+

|
|
730
|
+
|
|
731
|
+
## In the Browser ##
|
|
732
|
+
We can run memory_graph in the browser: <a href="https://bterwijn.github.io/memory_graph/src/pyodide.html" target="_blank">Pyodide Example</a>
|
|
733
|
+

|
|
711
734
|
|
|
712
735
|
## Troubleshooting ##
|
|
713
736
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -13,15 +13,15 @@ import sys
|
|
|
13
13
|
|
|
14
14
|
import graphviz
|
|
15
15
|
|
|
16
|
-
__version__ = "0.3.
|
|
16
|
+
__version__ = "0.3.10"
|
|
17
17
|
__author__ = 'Bas Terwijn'
|
|
18
18
|
render_filename = 'memory_graph.pdf'
|
|
19
|
-
|
|
19
|
+
block_prints_location = True
|
|
20
20
|
press_enter_message = "Press <Enter> to continue..."
|
|
21
21
|
|
|
22
|
-
def get_source_location(stack_index):
|
|
22
|
+
def get_source_location(stack_index=0):
|
|
23
23
|
""" Helper function to get the source location of the stack with 'stack_index' of the call stack. """
|
|
24
|
-
frameInfo = inspect.stack()[stack_index] # get frameInfo of calling frame
|
|
24
|
+
frameInfo = inspect.stack()[1+stack_index] # get frameInfo of calling frame
|
|
25
25
|
filename= frameInfo.filename
|
|
26
26
|
line_nr= frameInfo.lineno
|
|
27
27
|
function = frameInfo.function
|
|
@@ -32,14 +32,15 @@ def block(fun=None, *args, **kwargs):
|
|
|
32
32
|
Calls the given function `fun` with specified arguments and keyword arguments,
|
|
33
33
|
waits for the user to press Enter, and returns the result of `fun`.
|
|
34
34
|
"""
|
|
35
|
+
stack_index = 0
|
|
35
36
|
if 'stack_index' in kwargs:
|
|
36
37
|
stack_index = kwargs['stack_index']
|
|
37
38
|
del kwargs['stack_index']
|
|
38
39
|
result = None
|
|
39
40
|
if callable(fun):
|
|
40
41
|
result = fun(*args, **kwargs)
|
|
41
|
-
if memory_graph.
|
|
42
|
-
print('blocked at ' + get_source_location(stack_index), end=', ')
|
|
42
|
+
if memory_graph.block_prints_location:
|
|
43
|
+
print('blocked at ' + get_source_location(1+stack_index), end=', ')
|
|
43
44
|
if memory_graph.press_enter_message:
|
|
44
45
|
print(memory_graph.press_enter_message)
|
|
45
46
|
input()
|
|
@@ -58,7 +59,7 @@ def create_graph(data,
|
|
|
58
59
|
graphviz_graph = memory_to_nodes.memory_to_nodes(data)
|
|
59
60
|
return graphviz_graph
|
|
60
61
|
|
|
61
|
-
def show(data
|
|
62
|
+
def show(data, block=False,
|
|
62
63
|
colors = None,
|
|
63
64
|
vertical_orientations = None,
|
|
64
65
|
slicers = None):
|
|
@@ -82,84 +83,84 @@ def render(data, outfile=None, block=False,
|
|
|
82
83
|
|
|
83
84
|
# ------------ aliases
|
|
84
85
|
|
|
85
|
-
def sl(stack_index=
|
|
86
|
+
def sl(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
86
87
|
"""
|
|
87
88
|
Shows the graph of locals() and blocks.
|
|
88
89
|
"""
|
|
89
|
-
data =
|
|
90
|
+
data = get_locals_from_call_stack(stack_index=1+stack_index)
|
|
90
91
|
memory_graph.show(data, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
91
92
|
|
|
92
|
-
def ss(stack_index=
|
|
93
|
+
def ss(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
93
94
|
"""
|
|
94
95
|
Shows the graph of mg.get_call_stack() and blocks.
|
|
95
96
|
"""
|
|
96
|
-
data = get_call_stack(stack_index=stack_index)
|
|
97
|
+
data = get_call_stack(stack_index=1+stack_index)
|
|
97
98
|
memory_graph.show(data, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
98
99
|
|
|
99
|
-
def bsl(stack_index=
|
|
100
|
+
def bsl(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
100
101
|
"""
|
|
101
102
|
Shows the graph of locals() and blocks.
|
|
102
103
|
"""
|
|
103
|
-
data =
|
|
104
|
-
memory_graph.block(memory_graph.show, data, stack_index=stack_index
|
|
104
|
+
data = get_locals_from_call_stack(stack_index=1+stack_index)
|
|
105
|
+
memory_graph.block(memory_graph.show, data, stack_index=1+stack_index, block=False,
|
|
105
106
|
colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
106
107
|
|
|
107
|
-
def bss(stack_index=
|
|
108
|
+
def bss(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
108
109
|
"""
|
|
109
110
|
Shows the graph of mg.get_call_stack() and blocks.
|
|
110
111
|
"""
|
|
111
|
-
data = get_call_stack(stack_index=stack_index)
|
|
112
|
-
memory_graph.block(memory_graph.show, data, stack_index=stack_index
|
|
112
|
+
data = get_call_stack(stack_index=1+stack_index)
|
|
113
|
+
memory_graph.block(memory_graph.show, data, stack_index=1+stack_index, block=False,
|
|
113
114
|
colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
114
115
|
|
|
115
|
-
def rl(stack_index=
|
|
116
|
+
def rl(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
116
117
|
"""
|
|
117
118
|
Shows the graph of locals() and blocks.
|
|
118
119
|
"""
|
|
119
|
-
data =
|
|
120
|
+
data = get_locals_from_call_stack(stack_index=1+stack_index)
|
|
120
121
|
memory_graph.render(data, block=False, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
121
122
|
|
|
122
|
-
def rs(stack_index=
|
|
123
|
+
def rs(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
123
124
|
"""
|
|
124
125
|
Shows the graph of mg.get_call_stack() and blocks.
|
|
125
126
|
"""
|
|
126
|
-
data = get_call_stack(stack_index=stack_index)
|
|
127
|
+
data = get_call_stack(stack_index=1+stack_index)
|
|
127
128
|
memory_graph.render(data, block=False, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
128
129
|
|
|
129
|
-
def brl(stack_index=
|
|
130
|
+
def brl(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
130
131
|
"""
|
|
131
132
|
Shows the graph of locals() and blocks.
|
|
132
133
|
"""
|
|
133
|
-
data =
|
|
134
|
-
memory_graph.block(memory_graph.render, data, stack_index=stack_index
|
|
134
|
+
data = get_locals_from_call_stack(stack_index=1+stack_index)
|
|
135
|
+
memory_graph.block(memory_graph.render, data, stack_index=1+stack_index, block=False,
|
|
135
136
|
colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
136
137
|
|
|
137
|
-
def brs(stack_index=
|
|
138
|
+
def brs(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
138
139
|
"""
|
|
139
140
|
Shows the graph of mg.get_call_stack() and blocks.
|
|
140
141
|
"""
|
|
141
|
-
data = get_call_stack(stack_index=stack_index)
|
|
142
|
-
memory_graph.block(memory_graph.render, data, stack_index=stack_index
|
|
142
|
+
data = get_call_stack(stack_index=1+stack_index)
|
|
143
|
+
memory_graph.block(memory_graph.render, data, stack_index=1+stack_index, block=False,
|
|
143
144
|
colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
144
145
|
|
|
145
|
-
def l(stack_index=
|
|
146
|
+
def l(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
146
147
|
"""
|
|
147
148
|
Shows the graph of locals() and blocks.
|
|
148
149
|
"""
|
|
149
|
-
bsl(stack_index=stack_index
|
|
150
|
+
bsl(stack_index=1+stack_index, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
150
151
|
|
|
151
|
-
def s(stack_index=
|
|
152
|
+
def s(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
|
|
152
153
|
"""
|
|
153
154
|
Shows the graph of mg.get_call_stack() and blocks.
|
|
154
155
|
"""
|
|
155
|
-
bss(stack_index=stack_index
|
|
156
|
+
bss(stack_index=1+stack_index, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
|
|
156
157
|
|
|
157
158
|
|
|
158
159
|
# ------------ call stack
|
|
159
160
|
|
|
160
|
-
def
|
|
161
|
+
def get_locals_from_call_stack(stack_index=0):
|
|
161
162
|
""" Helper function to get locals of the stack with 'stack_inex' of the call stack. """
|
|
162
|
-
frameInfo = inspect.stack()[stack_index] # get frameInfo of calling frame
|
|
163
|
+
frameInfo = inspect.stack()[1+stack_index] # get frameInfo of calling frame
|
|
163
164
|
return frameInfo.frame.f_locals
|
|
164
165
|
|
|
165
166
|
def stack_frames_to_dict(frames):
|
|
@@ -169,10 +170,10 @@ def stack_frames_to_dict(frames):
|
|
|
169
170
|
return {f"{level}: {frameInfo.function}" : frameInfo.frame.f_locals
|
|
170
171
|
for level, frameInfo in enumerate(frames)}
|
|
171
172
|
|
|
172
|
-
def get_call_stack(up_to_function="<module>",stack_index=
|
|
173
|
+
def get_call_stack(up_to_function="<module>",stack_index=0):
|
|
173
174
|
""" Gets the call stack up to the function 'up_to_function'. """
|
|
174
175
|
frames = reversed(list(
|
|
175
|
-
utils.take_up_to(lambda i: i.function==up_to_function, inspect.stack()[stack_index:])
|
|
176
|
+
utils.take_up_to(lambda i: i.function==up_to_function, inspect.stack()[1+stack_index:])
|
|
176
177
|
))
|
|
177
178
|
return stack_frames_to_dict(frames)
|
|
178
179
|
|
|
@@ -197,27 +198,52 @@ def get_call_stack_pycharm(after_function="trace_dispatch",up_to_function="<modu
|
|
|
197
198
|
return get_call_stack_after_up_to(after_function,up_to_function)
|
|
198
199
|
|
|
199
200
|
def save_call_stack(filename):
|
|
200
|
-
"""
|
|
201
|
+
""" Saves the call stack to 'filename' for inspection to see what functions need to be
|
|
201
202
|
filtered out to create the desired graph. """
|
|
202
203
|
with open(filename,'w') as file:
|
|
203
|
-
for
|
|
204
|
-
file.write(f"function:{
|
|
205
|
-
|
|
204
|
+
for frame in inspect.stack():
|
|
205
|
+
file.write(f"function:{frame.function} filename:{frame.filename}\n")
|
|
206
|
+
|
|
207
|
+
def print_call_stack_vars(stack_index=0):
|
|
208
|
+
""" Prints all variables on the call stack. """
|
|
209
|
+
for level, frameInfo in enumerate(reversed(inspect.stack())):
|
|
210
|
+
print('=====',level,frameInfo.function)
|
|
211
|
+
print(tuple(frameInfo.frame.f_locals.keys()))
|
|
212
|
+
|
|
206
213
|
# ------------ jupyter filtering
|
|
207
|
-
|
|
214
|
+
|
|
208
215
|
jupyter_filter_keys = {'exit','quit','v','In','Out','jupyter_filter_keys'}
|
|
209
216
|
def jupyter_locals_filter(jupyter_locals):
|
|
210
217
|
""" Filter out the jupyter specific keys that polute the graph. """
|
|
211
218
|
return {k:v for k,v in jupyter_locals.items()
|
|
212
219
|
if k not in jupyter_filter_keys and k[0] != '_'}
|
|
213
220
|
|
|
214
|
-
def locals_jupyter(stack_index=
|
|
221
|
+
def locals_jupyter(stack_index=0):
|
|
215
222
|
""" Get the locals of the calling frame in a jupyter notebook, filtering out the jupyter specific keys. """
|
|
216
|
-
return jupyter_locals_filter(
|
|
223
|
+
return jupyter_locals_filter(get_locals_from_call_stack(1+stack_index))
|
|
217
224
|
|
|
218
|
-
def get_call_stack_jupyter(up_to_function="<module>",stack_index=
|
|
225
|
+
def get_call_stack_jupyter(up_to_function="<module>",stack_index=0):
|
|
219
226
|
""" Get the call stack in a jupyter notebook, filtering out the jupyter specific keys. """
|
|
220
|
-
call_stack = get_call_stack(up_to_function,stack_index)
|
|
227
|
+
call_stack = get_call_stack(up_to_function,1+stack_index)
|
|
221
228
|
globals_frame = next(iter(call_stack))
|
|
222
229
|
call_stack[globals_frame] = jupyter_locals_filter(call_stack[globals_frame])
|
|
223
230
|
return call_stack
|
|
231
|
+
|
|
232
|
+
# ------------ ipython filtering
|
|
233
|
+
|
|
234
|
+
ipython_filter_keys = {'mg_visualization_status', 'sys', 'ipython', 'In', 'Out', 'get_ipython', 'exit', 'quit', 'open'}
|
|
235
|
+
def ipython_locals_filter(ipython_locals):
|
|
236
|
+
""" Filter out the ipython specific keys that polute the graph. """
|
|
237
|
+
return {k:v for k,v in ipython_locals.items()
|
|
238
|
+
if k not in ipython_filter_keys and k[0] != '_'}
|
|
239
|
+
|
|
240
|
+
def locals_ipython(stack_index=0):
|
|
241
|
+
""" Get the locals of the calling frame in a ipython, filtering out the ipython specific keys. """
|
|
242
|
+
return ipython_locals_filter(get_locals_from_call_stack(1+stack_index))
|
|
243
|
+
|
|
244
|
+
def get_call_stack_ipython(up_to_function="<module>",stack_index=0):
|
|
245
|
+
""" Get the call stack in a ipython, filtering out the ipython specific keys. """
|
|
246
|
+
call_stack = get_call_stack(up_to_function,1+stack_index)
|
|
247
|
+
globals_frame = next(iter(call_stack))
|
|
248
|
+
call_stack[globals_frame] = ipython_locals_filter(call_stack[globals_frame])
|
|
249
|
+
return call_stack
|
|
@@ -15,7 +15,7 @@ import memory_graph.utils as utils
|
|
|
15
15
|
|
|
16
16
|
import types
|
|
17
17
|
|
|
18
|
-
""" The maximum depth of nodes in the graph. When the graph gets too big set this to a small positive number. A
|
|
18
|
+
""" The maximum depth of nodes in the graph. When the graph gets too big set this to a small positive number. A `✂` symbol indictes where the graph is cut short. """
|
|
19
19
|
config.max_tree_depth = -1
|
|
20
20
|
|
|
21
21
|
config.max_missing_edges = 3
|
|
@@ -23,6 +23,9 @@ config.max_missing_edges = 3
|
|
|
23
23
|
""" The maximum length of strings shown in the graph. Longer strings will be truncated. """
|
|
24
24
|
config.max_string_length = 42
|
|
25
25
|
|
|
26
|
+
""" The number of references keeping child nodes in order versus other references pullen them out. """
|
|
27
|
+
config.graph_stability = 10
|
|
28
|
+
|
|
26
29
|
""" Types that by default will not have references pointing to them in the graph but instead will be visualized in the node of their parent. """
|
|
27
30
|
config.not_node_types = {
|
|
28
31
|
type(None), bool, int, float, complex, str,
|
|
@@ -133,8 +133,7 @@ def create_depth_of_nodes(nodes, nodes_at_depth):
|
|
|
133
133
|
def add_subgraph(graphviz_graph, nodes_to_subgraph):
|
|
134
134
|
new_node_names = [node.get_name() for node in nodes_to_subgraph]
|
|
135
135
|
if len(new_node_names) > 1:
|
|
136
|
-
|
|
137
|
-
graphviz_graph.body.append('{ rank="same" '+('; '.join(new_node_names))+'; }\n')
|
|
136
|
+
graphviz_graph.body.append('subgraph { rank=same; '+ ' -> '.join(new_node_names) + '[weight='+str(config.graph_stability)+', style=invis]; }\n')
|
|
138
137
|
|
|
139
138
|
def add_to_graphviz_graph(graphviz_graph, nodes, node, slices, id_to_slices, subgraphed_nodes, depth):
|
|
140
139
|
html_table = node.get_html_table(nodes, slices, id_to_slices)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: memory_graph
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
4
4
|
Summary: Draws a graph of your data to analyze its structure.
|
|
5
5
|
Home-page: https://github.com/bterwijn/memory_graph
|
|
6
6
|
Author: Bas Terwijn
|
|
@@ -91,7 +91,7 @@ mg.render(data, "my_graph.pdf")
|
|
|
91
91
|
mg.render(data, "my_graph.svg")
|
|
92
92
|
mg.render(data, "my_graph.png")
|
|
93
93
|
mg.render(data, "my_graph.gv") # Graphviz DOT file
|
|
94
|
-
mg.render(data) # renders to 'mg.render_filename'
|
|
94
|
+
mg.render(data) # renders to 'mg.render_filename' with default value: 'memory_graph.pdf'
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
# Chapters #
|
|
@@ -112,6 +112,10 @@ mg.render(data) # renders to 'mg.render_filename' (default value: 'memory_graph.
|
|
|
112
112
|
|
|
113
113
|
[Jupyter Notebook](#jupyter-notebook)
|
|
114
114
|
|
|
115
|
+
[ipython](#ipython)
|
|
116
|
+
|
|
117
|
+
[In the Browser](#in-the-browser)
|
|
118
|
+
|
|
115
119
|
[Troubleshooting](#troubleshooting)
|
|
116
120
|
|
|
117
121
|
|
|
@@ -152,7 +156,7 @@ mg.render(locals(), 'immutable2.png')
|
|
|
152
156
|
|
|
153
157
|
|
|
154
158
|
### Mutable Type ###
|
|
155
|
-
With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `b`
|
|
159
|
+
With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `a` and `b` are independent.
|
|
156
160
|
|
|
157
161
|
```python
|
|
158
162
|
import memory_graph as mg
|
|
@@ -167,7 +171,7 @@ mg.render(locals(), 'mutable2.png')
|
|
|
167
171
|
|:-----------------------------------------------------------:|:-------------------------------------------------------------:|
|
|
168
172
|
| mutable1.png | mutable2.png |
|
|
169
173
|
|
|
170
|
-
One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type
|
|
174
|
+
One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type can be large, making it inefficient to copy each time we change it. Immutable values generally don't need to change as much, or are small which makes copying less of a concern.
|
|
171
175
|
|
|
172
176
|
### Copying ###
|
|
173
177
|
Python offers three different "copy" options that we will demonstrate using a nested list:
|
|
@@ -220,7 +224,7 @@ mg.show(locals())
|
|
|
220
224
|
|
|
221
225
|
|
|
222
226
|
## Call Stack ##
|
|
223
|
-
The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters
|
|
227
|
+
The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters `a`, `b`, and `c`.
|
|
224
228
|
|
|
225
229
|
```python
|
|
226
230
|
import memory_graph as mg
|
|
@@ -254,9 +258,15 @@ It is often helpful to temporarily block program execution to inspect the graph.
|
|
|
254
258
|
mg.block(fun, arg1, arg2, ...)
|
|
255
259
|
```
|
|
256
260
|
|
|
257
|
-
This function
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
This function:
|
|
262
|
+
* first executes `fun(arg1, arg2, ...)`
|
|
263
|
+
* then prints the current source location in the program
|
|
264
|
+
* then blocks execution until the <Enter> key is pressed
|
|
265
|
+
* finally returns the value of the `fun()` call
|
|
266
|
+
|
|
267
|
+
to change it's behavior:
|
|
268
|
+
* Set `mg.block_prints_location = False` to skip printing the source location.
|
|
269
|
+
* Set `mg.press_enter_message = None` to skip printing "Press <Enter> to continue...".
|
|
260
270
|
|
|
261
271
|
### Recursion ###
|
|
262
272
|
The call stack is also helpful to visualize how recursion works. Here we use `mg.block()` to show each step of how recursively ```factorial(3)``` is computed:
|
|
@@ -620,9 +630,9 @@ mg.show(locals())
|
|
|
620
630
|
```
|
|
621
631
|

|
|
622
632
|
|
|
623
|
-
Next figure out what are the attributes you want to graph and choose a Node type, there are four options
|
|
633
|
+
Next figure out what are the attributes you want to graph and choose a Node type, there are four options:
|
|
624
634
|
|
|
625
|
-
### 1 Node_Base ###
|
|
635
|
+
### 1) Node_Base ###
|
|
626
636
|
Node_base is a leaf node (with no children) and shows just a single value.
|
|
627
637
|
```python
|
|
628
638
|
import memory_graph as mg
|
|
@@ -642,7 +652,7 @@ mg.show(locals())
|
|
|
642
652
|
```
|
|
643
653
|

|
|
644
654
|
|
|
645
|
-
### 2 Node_Linear ###
|
|
655
|
+
### 2) Node_Linear ###
|
|
646
656
|
Node_Linear shows all the values in a line like a list.
|
|
647
657
|
```python
|
|
648
658
|
import memory_graph as mg
|
|
@@ -666,7 +676,7 @@ mg.show(locals())
|
|
|
666
676
|
```
|
|
667
677
|

|
|
668
678
|
|
|
669
|
-
### 3 Node_Key_Value ###
|
|
679
|
+
### 3) Node_Key_Value ###
|
|
670
680
|
Node_Key_Value shows key-value pairs like a dictionary. Note the required `items()` call at the end.
|
|
671
681
|
```python
|
|
672
682
|
import memory_graph as mg
|
|
@@ -690,7 +700,7 @@ mg.show(locals())
|
|
|
690
700
|
```
|
|
691
701
|

|
|
692
702
|
|
|
693
|
-
### 4 Node_Table ###
|
|
703
|
+
### 4) Node_Table ###
|
|
694
704
|
Node_Table shows all the values as a table.
|
|
695
705
|
```python
|
|
696
706
|
import memory_graph as mg
|
|
@@ -727,6 +737,19 @@ mg.block(display, mg.create_graph(mg.locals_jupyter()) ) # the same but blocked
|
|
|
727
737
|
See for example [jupyter_example.ipynb](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.ipynb).
|
|
728
738
|

|
|
729
739
|
|
|
740
|
+
## ipython ##
|
|
741
|
+
In ipython `locals()` has additional variables that cause problems in the graph, use `mg.locals_ipython()` to get the local variables with these problematic variables filtered out. Use `mg.get_call_stack_ipython()` to get the whole call stack with these variables filtered out.
|
|
742
|
+
|
|
743
|
+
Additionally install file [auto_memory_graph.py](https://raw.githubusercontent.com/bterwijn/memory_graph/main/sc/auto_memory_graph.py) in the ipython startup directory:
|
|
744
|
+
* Linux/Mac: ~/.ipython/profile_default/startup/
|
|
745
|
+
* Windows: %USERPROFILE%\.ipython\profile_default\startup\
|
|
746
|
+
|
|
747
|
+
Then after starting 'ipython' call function `mg_switch()` to turn on/off the automatic visualization of local variables after each command.
|
|
748
|
+

|
|
749
|
+
|
|
750
|
+
## In the Browser ##
|
|
751
|
+
We can run memory_graph in the browser: <a href="https://bterwijn.github.io/memory_graph/src/pyodide.html" target="_blank">Pyodide Example</a>
|
|
752
|
+

|
|
730
753
|
|
|
731
754
|
## Troubleshooting ##
|
|
732
755
|
|
|
@@ -36,6 +36,7 @@ images/highlight.py
|
|
|
36
36
|
images/immutable.py
|
|
37
37
|
images/immutable1.png
|
|
38
38
|
images/immutable2.png
|
|
39
|
+
images/ipython.png
|
|
39
40
|
images/jupyter_example.ipynb
|
|
40
41
|
images/jupyter_example.png
|
|
41
42
|
images/linked_list.png
|
|
@@ -47,6 +48,7 @@ images/mutable1.png
|
|
|
47
48
|
images/mutable2.png
|
|
48
49
|
images/power_set.gif
|
|
49
50
|
images/power_set.py
|
|
51
|
+
images/pyodide.png
|
|
50
52
|
images/uva.png
|
|
51
53
|
memory_graph/__init__.py
|
|
52
54
|
memory_graph/config.py
|
|
@@ -79,4 +81,6 @@ memory_graph.egg-info/SOURCES.txt
|
|
|
79
81
|
memory_graph.egg-info/dependency_links.txt
|
|
80
82
|
memory_graph.egg-info/requires.txt
|
|
81
83
|
memory_graph.egg-info/top_level.txt
|
|
84
|
+
src/auto_memory_graph.py
|
|
85
|
+
src/pyodide.html
|
|
82
86
|
uml/memory_graph.uxf
|
|
@@ -11,7 +11,7 @@ long_description_from_readme = (this_directory / "README.md").read_text()
|
|
|
11
11
|
|
|
12
12
|
setup(
|
|
13
13
|
name = 'memory_graph',
|
|
14
|
-
version = '0.3.
|
|
14
|
+
version = '0.3.10',
|
|
15
15
|
description = 'Draws a graph of your data to analyze its structure.',
|
|
16
16
|
long_description = long_description_from_readme,
|
|
17
17
|
long_description_content_type = 'text/markdown',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import memory_graph as mg
|
|
2
|
+
|
|
3
|
+
mg_visualization_status = False
|
|
4
|
+
print('running:', __file__)
|
|
5
|
+
print('Call mg_switch() to turn on/off auto memory_graph visualization.')
|
|
6
|
+
|
|
7
|
+
def mg_visualization(execution_result):
|
|
8
|
+
ipython_locals = get_ipython().user_ns
|
|
9
|
+
mg.show(mg.ipython_locals_filter(ipython_locals))
|
|
10
|
+
|
|
11
|
+
def mg_switch():
|
|
12
|
+
global mg_visualization_status
|
|
13
|
+
mg_visualization_status = not mg_visualization_status
|
|
14
|
+
if mg_visualization_status:
|
|
15
|
+
get_ipython().events.register("post_run_cell", mg_visualization)
|
|
16
|
+
else:
|
|
17
|
+
get_ipython().events.unregister("post_run_cell", mg_visualization)
|
|
18
|
+
return mg_visualization_status
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Pyodide Python Runner</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/viz.js"></script>
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/full.render.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
font-family: Arial, sans-serif;
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
height: 100%;
|
|
16
|
+
}
|
|
17
|
+
.container {
|
|
18
|
+
display: grid;
|
|
19
|
+
grid-template-columns: 1fr 1fr;
|
|
20
|
+
grid-template-rows: 1fr 1fr;
|
|
21
|
+
height: 100vh;
|
|
22
|
+
}
|
|
23
|
+
.part {
|
|
24
|
+
border: 1px solid black;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
justify-content: flex-start;
|
|
28
|
+
align-items: flex-start;
|
|
29
|
+
padding: 10px;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}
|
|
32
|
+
textarea {
|
|
33
|
+
flex: 1;
|
|
34
|
+
width: 100%;
|
|
35
|
+
resize: none;
|
|
36
|
+
margin-bottom: 10px;
|
|
37
|
+
}
|
|
38
|
+
button {
|
|
39
|
+
padding: 10px 15px;
|
|
40
|
+
font-size: 16px;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
}
|
|
43
|
+
#output {
|
|
44
|
+
background-color: #f4f4f4;
|
|
45
|
+
border: 1px solid #ddd;
|
|
46
|
+
white-space: pre-wrap;
|
|
47
|
+
}
|
|
48
|
+
#log {
|
|
49
|
+
background-color: #f4f4f4;
|
|
50
|
+
border: 1px solid #ddd;
|
|
51
|
+
white-space: pre-wrap;
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
54
|
+
</head>
|
|
55
|
+
<body>
|
|
56
|
+
<div class="container">
|
|
57
|
+
<div class="part"><!-- ========== Top Left ========== --> <p>Log:</p>
|
|
58
|
+
<textarea id="log"></textarea>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="part"><!-- ========== Top Right ========== --> <p>Python Code:</p>
|
|
62
|
+
<textarea id="python-code">
|
|
63
|
+
import memory_graph as mg
|
|
64
|
+
import js
|
|
65
|
+
|
|
66
|
+
def add_one(a, b, c):
|
|
67
|
+
a += [1]
|
|
68
|
+
b += (1,)
|
|
69
|
+
c += [1]
|
|
70
|
+
print("display the call stack")
|
|
71
|
+
js.display(mg.create_graph(mg.get_call_stack()))
|
|
72
|
+
|
|
73
|
+
print("initialize test data")
|
|
74
|
+
a = [4, 3, 2]
|
|
75
|
+
b = (4, 3, 2)
|
|
76
|
+
c = [4, 3, 2]
|
|
77
|
+
print(f"a:{a} b:{b} c:{c}")
|
|
78
|
+
|
|
79
|
+
print("call function add_one()")
|
|
80
|
+
add_one(a, b, c.copy())
|
|
81
|
+
print(f"a:{a} b:{b} c:{c}")
|
|
82
|
+
</textarea>
|
|
83
|
+
<button id="run-button" disabled>Run</button>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="part"><!-- ========== Bottom Left ========== --> <p>Graph:</p>
|
|
87
|
+
<div id="graph-container"></div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
<div class="part"><!-- ========== Bottom Right ========== --> <p>Output:</p>
|
|
92
|
+
<textarea id="output"></textarea>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
<script>
|
|
98
|
+
window.display = function(graph) {
|
|
99
|
+
const svgContainer = document.getElementById("graph-container");
|
|
100
|
+
try {
|
|
101
|
+
// Use Viz.js to render the DOT code into SVG
|
|
102
|
+
const viz = new Viz();
|
|
103
|
+
viz.renderSVGElement(graph.source)
|
|
104
|
+
.then(svgElement => {
|
|
105
|
+
svgContainer.innerHTML = "";
|
|
106
|
+
svgContainer.appendChild(svgElement);
|
|
107
|
+
})
|
|
108
|
+
.catch(error => {
|
|
109
|
+
svgContainer.innerHTML = `Error: ${error}`;
|
|
110
|
+
});
|
|
111
|
+
} catch (error) {
|
|
112
|
+
svgContainer.innerHTML = `Error: ${error}`;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let memory_graph_ready = false;
|
|
117
|
+
let pyodide;
|
|
118
|
+
const logDiv = document.getElementById("log");
|
|
119
|
+
logDiv.textContent = "Setting up\n";
|
|
120
|
+
|
|
121
|
+
async function initializePyodide() {
|
|
122
|
+
// Load Pyodide and micropip
|
|
123
|
+
logDiv.textContent += "Loading Pyodide... "
|
|
124
|
+
pyodide = await loadPyodide();
|
|
125
|
+
logDiv.textContent +="loaded\n";
|
|
126
|
+
logDiv.textContent += "Loading micropip... "
|
|
127
|
+
await pyodide.loadPackage("micropip");
|
|
128
|
+
logDiv.textContent += "loaded\n";
|
|
129
|
+
const micropip = pyodide.pyimport("micropip");
|
|
130
|
+
|
|
131
|
+
// Installing memory_graph
|
|
132
|
+
try {
|
|
133
|
+
logDiv.textContent += "Installing memory_graph... "
|
|
134
|
+
await micropip.install("memory_graph");
|
|
135
|
+
logDiv.textContent += "installed\n";
|
|
136
|
+
memory_graph_ready = true;
|
|
137
|
+
|
|
138
|
+
if (memory_graph_ready) {
|
|
139
|
+
logDiv.textContent += "Setup complete, ready to Run\n";
|
|
140
|
+
const button = document.getElementById("run-button");
|
|
141
|
+
button.disabled = false;
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
logDiv.textContent += `Failed to install memory_graph: ${error.message}\n`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
initializePyodide(); // Call the initialization function.
|
|
150
|
+
|
|
151
|
+
document.getElementById("run-button").addEventListener("click", async () => {
|
|
152
|
+
const code = document.getElementById("python-code").value;
|
|
153
|
+
const outputDiv = document.getElementById("output");
|
|
154
|
+
outputDiv.textContent = ""; // Clear previous output
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// Redirect Python stdout to capture `print()` output
|
|
158
|
+
pyodide.runPython(`
|
|
159
|
+
import sys
|
|
160
|
+
import io
|
|
161
|
+
sys.stdout = io.StringIO()
|
|
162
|
+
sys.stderr = sys.stdout
|
|
163
|
+
`);
|
|
164
|
+
|
|
165
|
+
// Execute the user-provided code
|
|
166
|
+
logDiv.textContent += "running code\n"
|
|
167
|
+
await pyodide.runPythonAsync(code);
|
|
168
|
+
|
|
169
|
+
// Retrieve stdout content
|
|
170
|
+
const output = pyodide.runPython("sys.stdout.getvalue()");
|
|
171
|
+
outputDiv.textContent = `${output}`;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
outputDiv.textContent = `${error}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
});
|
|
177
|
+
</script>
|
|
178
|
+
</body>
|
|
179
|
+
</html>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|