memory-graph 0.3.53__tar.gz → 0.3.55__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.53/memory_graph.egg-info → memory_graph-0.3.55}/PKG-INFO +43 -27
- {memory_graph-0.3.53 → memory_graph-0.3.55}/README.md +42 -26
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/binary.py +1 -1
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/__init__.py +1 -1
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/config.py +1 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/config_default.py +6 -4
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/config_helpers.py +8 -1
- {memory_graph-0.3.53 → memory_graph-0.3.55/memory_graph.egg-info}/PKG-INFO +43 -27
- {memory_graph-0.3.53 → memory_graph-0.3.55}/pyproject.toml +1 -1
- {memory_graph-0.3.53 → memory_graph-0.3.55}/LICENSE.txt +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/MANIFEST.in +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/add_one.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/add_one.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/avltree.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/avltree_dir.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/avltree_fail.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/avltree_key_value.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/avltree_leaf.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/avltree_linear.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/avltree_table.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/bin_search.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/bin_search.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/bin_search_linear.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/bin_tree.gif +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/bin_tree.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/bin_tree.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/binary.gif +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/colab_example.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/copy_immutable.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/copy_immutable.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/copy_method.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/copy_method.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/copy_mix.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/copy_mix.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/copy_mutable.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/copy_mutable.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/create_gif.sh +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/create_images.sh +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/debug_vscode.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/debugging.gif +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/debugging.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/embedded1.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/embedded2.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/extension_numpy.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/extension_numpy.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/extension_pandas.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/extension_pandas.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/extension_torch.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/extension_torch.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/factorial.gif +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/factorial.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/hash_set.gif +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/hash_set.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/hash_set.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/hidden_edges.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/hidden_edges.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/immutable.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/immutable1.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/immutable2.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/introspect_depth.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/introspect_depth.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/ipython.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/jupyter_example.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/linked_list.gif +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/linked_list.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/linked_list.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/many_types.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/many_types.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/marimo_example.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/memory_graph_web_debugger.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/mutable.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/mutable1.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/mutable2.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/name_rebinding.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/not_node_types.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/power_set.gif +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/power_set.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/rebinding1.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/rebinding2.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/uva.png +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/images/vscode_copying.gif +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/call_stack.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/extension_numpy.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/extension_pandas.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/extension_torch.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/html_table.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/list_view.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/memory_to_nodes.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/node_base.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/node_key_value.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/node_leaf.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/node_linear.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/node_table.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/sequence.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/slicer.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/slices.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/slices_iterator.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/slices_table_iterator.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/test.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/test_max_graph_depth.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/test_memory_graph.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/test_memory_to_nodes.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/test_sequence.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/test_slicer.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/test_slices.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/test_slices_iterator.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph/utils.py +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph.egg-info/SOURCES.txt +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph.egg-info/dependency_links.txt +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph.egg-info/requires.txt +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/memory_graph.egg-info/top_level.txt +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/setup.cfg +0 -0
- {memory_graph-0.3.53 → memory_graph-0.3.55}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memory_graph
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.55
|
|
4
4
|
Summary: Teaching tool and debugging aid in context of references, mutable data types, and shallow and deep copy.
|
|
5
5
|
Author-email: Bas Terwijn <bterwijn@gmail.com>
|
|
6
6
|
License: BSD 2-Clause License
|
|
@@ -52,12 +52,16 @@ Additionally [Graphviz](https://graphviz.org/download/) needs to be installed.
|
|
|
52
52
|
|
|
53
53
|
# Highlights #
|
|
54
54
|

|
|
55
|
-
Run a live demo in the 👉 [**Memory Graph Web Debugger**](https://memory-graph.com/#play) 👈 now, no installation required!
|
|
55
|
+
Run a live demo in the 👉 [**Memory Graph Web Debugger**](https://memory-graph.com/#breakpoints=8&continues=1×tep=1.0&play) 👈 now, no installation required!
|
|
56
56
|
|
|
57
57
|
- learn the right **mental model** to think about Python data (references, mutability, shallow vs deep copy)
|
|
58
58
|
- **visualize the structure of your data** to easily understand and debug any data structure
|
|
59
59
|
- understand function calls, variable scope, and the **complete program state** through call stack visualization
|
|
60
60
|
|
|
61
|
+
An example Binary Tree data structure:
|
|
62
|
+

|
|
63
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/bin_tree.py×tep=0.2&play).
|
|
64
|
+
|
|
61
65
|
# Videos #
|
|
62
66
|
| [](https://www.youtube.com/watch?v=23_bHcr7hqo) | [](https://www.youtube.com/watch?v=pvIJgHCaXhU) |
|
|
63
67
|
|:--:|:--:|
|
|
@@ -132,7 +136,7 @@ identical?: True
|
|
|
132
136
|
```
|
|
133
137
|
A better way to understand what values are shared is to draw a graph using [memory_graph](https://pypi.org/project/memory-graph/).
|
|
134
138
|
|
|
135
|
-
#
|
|
139
|
+
# Topics #
|
|
136
140
|
|
|
137
141
|
[Python Data Model](#python-data-model)
|
|
138
142
|
|
|
@@ -164,8 +168,6 @@ A better way to understand what values are shared is to draw a graph using [memo
|
|
|
164
168
|
|
|
165
169
|
[Troubleshooting](#troubleshooting)
|
|
166
170
|
|
|
167
|
-
[Social Media](#social-media)
|
|
168
|
-
|
|
169
171
|
[Other Packages](#other-packages)
|
|
170
172
|
|
|
171
173
|
## Author ##
|
|
@@ -174,6 +176,10 @@ Bas Terwijn
|
|
|
174
176
|
## Inspiration ##
|
|
175
177
|
Inspired by [Python Tutor](https://pythontutor.com/).
|
|
176
178
|
|
|
179
|
+
## Social Media #
|
|
180
|
+
* [LinkedIn](https://www.linkedin.com/groups/13244150/)
|
|
181
|
+
* [Reddit](https://www.reddit.com/r/Python_memory_graph/)
|
|
182
|
+
|
|
177
183
|
## Supported by ##
|
|
178
184
|
<img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/uva.png" alt="University of Amsterdam" width="600">
|
|
179
185
|
|
|
@@ -274,6 +280,8 @@ mg.show(locals())
|
|
|
274
280
|
```
|
|
275
281
|

|
|
276
282
|
|
|
283
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/custom_copy.py&breakpoints=15&continues=1&play).
|
|
284
|
+
|
|
277
285
|
## Name Rebinding ##
|
|
278
286
|
When `a` and `b` share a mutable value, then changing the value of `b` changes the value of `a` and vice versa. However, reassigning `b` does not change `a`. When you reassign `b`, you only rebind the name `b` to another value without effecting any other variables.
|
|
279
287
|
|
|
@@ -348,6 +356,8 @@ print(f"a:{a} b:{b} c:{c}")
|
|
|
348
356
|
```
|
|
349
357
|

|
|
350
358
|
|
|
359
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/function_call.py&play).
|
|
360
|
+
|
|
351
361
|
In the printed output only `a` is changed as a result of the function call:
|
|
352
362
|
```
|
|
353
363
|
a:[4, 3, 2, 1] b:(4, 3, 2) c:[4, 3, 2]
|
|
@@ -355,6 +365,8 @@ a:[4, 3, 2, 1] b:(4, 3, 2) c:[4, 3, 2]
|
|
|
355
365
|
|
|
356
366
|
This is because `b` is of immutable type 'tuple' so its value gets copied automatically when it is changed. And because the function is called with a copy of `c`, its original value is not changed by the function. The value of variable `a` is the only value of mutable type that is shared between the root stack frame **'0: \<module>'** and the **'1: add_one'** stack frame of the function so only that variable is affected as a result of the function call. The other changes remain confined to the local variables of the ```add_one()``` function.
|
|
357
367
|
|
|
368
|
+
Now is a good time to practice the Python Data Model. Here are [some exercises](https://github.com/bterwijn/memory_graph_videos/blob/main/exercises/exercises.md) on references, mutability, copies, and function calls.
|
|
369
|
+
|
|
358
370
|
## Block ##
|
|
359
371
|
It is often helpful to temporarily block program execution to inspect the graph. For this we can use the `mg.block()` function:
|
|
360
372
|
|
|
@@ -384,16 +396,17 @@ def factorial(n):
|
|
|
384
396
|
|
|
385
397
|
print( factorial(4) )
|
|
386
398
|
```
|
|
387
|
-
|
|
388
399
|

|
|
389
400
|
|
|
390
401
|
and the result is: 1 x 2 x 3 x 4 = 24
|
|
391
402
|
|
|
392
|
-
|
|
393
|
-
|
|
403
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/factorial.py×tep=1.0&play).
|
|
404
|
+
|
|
405
|
+
## Binary Conversion ##
|
|
406
|
+
A more interesting recursive example is function `binary()` that converts a integer from decimal to binary representation.
|
|
394
407
|
```python
|
|
395
408
|
import memory_graph as mg
|
|
396
|
-
mg.config.
|
|
409
|
+
mg.config.type_to_horizontal[list] = True # horizontal lists
|
|
397
410
|
|
|
398
411
|
def binary(value: int) -> list[int]:
|
|
399
412
|
mg.block(mg.show(), mg.stack())
|
|
@@ -408,9 +421,11 @@ print( binary(100) )
|
|
|
408
421
|
```
|
|
409
422
|

|
|
410
423
|
```
|
|
411
|
-
|
|
424
|
+
[1, 1, 0, 0, 1, 0, 0]
|
|
412
425
|
```
|
|
413
426
|
|
|
427
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/binary_convert.py×tep=1.0&play).
|
|
428
|
+
|
|
414
429
|
## Power Set ##
|
|
415
430
|
A more complex recursive example is function `power_set()` where lists are shared by different function calls. A power set is the set of all subsets of a collection of values.
|
|
416
431
|
|
|
@@ -441,6 +456,8 @@ print( power_set(['a', 'b', 'c']) )
|
|
|
441
456
|
[['a', 'b', 'c'], ['a', 'b'], ['a', 'c'], ['a'], ['b', 'c'], ['b'], ['c'], []]
|
|
442
457
|
```
|
|
443
458
|
|
|
459
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/power_set.py×tep=1.0&play).
|
|
460
|
+
|
|
444
461
|
# Debugging #
|
|
445
462
|
|
|
446
463
|
For the best debugging experience with memory_graph set for example expression:
|
|
@@ -639,10 +656,10 @@ for i in range(n):
|
|
|
639
656
|
Here we show values being inserted in a HashSet in PyCharm. When inserting the last value '44' we "Step Into" the code to show more of the details.
|
|
640
657
|

|
|
641
658
|
|
|
642
|
-
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/hash_set.py×tep=0.
|
|
659
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/hash_set.py&breakpoints=32&continues=1×tep=0.5&play).
|
|
643
660
|
|
|
644
661
|
# Configuration #
|
|
645
|
-
Different aspects of memory_graph can be configured. The default configuration can be reset by calling 'mg.config_default.reset()'.
|
|
662
|
+
Different aspects of memory_graph can be configured. The default configuration can be reset by calling 'mg.config_default.reset()'. The Memory Graph Web Debugger gives examples of the [most important configurations](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/config.py&play).
|
|
646
663
|
|
|
647
664
|
- ***mg.config.reopen_viewer*** : bool
|
|
648
665
|
- If True the viewer is reopened each time show() is called, this might change window focus, default True.
|
|
@@ -677,8 +694,8 @@ Different aspects of memory_graph can be configured. The default configuration c
|
|
|
677
694
|
- ***mg.config.type_to_color*** : dict[type, color]
|
|
678
695
|
- Maps a type to the [graphviz color](https://graphviz.org/doc/info/colors.html) it gets in the graph.
|
|
679
696
|
|
|
680
|
-
- ***mg.config.
|
|
681
|
-
- Maps a type to its orientation. Use 'True' for
|
|
697
|
+
- ***mg.config.type_to_horizontal*** : dict[type, bool]
|
|
698
|
+
- Maps a type to its orientation for Node_Linear and Node_Key_Value. Use 'True' for horizontal and 'False' for vertical. If not specified these nodes vertical unless they have references to children.
|
|
682
699
|
|
|
683
700
|
- ***mg.config.type_to_slicer*** : dict[type, int]
|
|
684
701
|
- Maps a type to a Slicer. A slicer determines how many elements of a data type are shown in the graph to prevent the graph from getting too big. 'Slicer()' does no slicing, 'Slicer(1,2,3)' shows just 1 element at the beginning, 2 in the middle, and 3 at the end.
|
|
@@ -735,7 +752,6 @@ mg.show(locals())
|
|
|
735
752
|
```
|
|
736
753
|

|
|
737
754
|
|
|
738
|
-
|
|
739
755
|
## All attributes using dir() ##
|
|
740
756
|
A useful start is to give it some color, show the list of all its attributes using `dir()`, and setting an empty Slicer to see the attribute list in full.
|
|
741
757
|
|
|
@@ -757,7 +773,7 @@ mg.config.type_to_slicer[bintrees.avltree.Node] = mg.Slicer()
|
|
|
757
773
|
|
|
758
774
|
mg.show(locals())
|
|
759
775
|
```
|
|
760
|
-

|
|
761
777
|
|
|
762
778
|
Next figure out what the attributes are you want to graph and choose a Node type, there are four options:
|
|
763
779
|
|
|
@@ -780,7 +796,7 @@ mg.config.type_to_node[bintrees.avltree.Node] = lambda data: mg.Node_Leaf(data,
|
|
|
780
796
|
|
|
781
797
|
mg.show(locals())
|
|
782
798
|
```
|
|
783
|
-

|
|
784
800
|
|
|
785
801
|
## 2) Node_Linear ##
|
|
786
802
|
Node_Linear shows multiple values in a line like a list.
|
|
@@ -804,7 +820,7 @@ mg.config.type_to_node[bintrees.avltree.Node] = lambda data: mg.Node_Linear(data
|
|
|
804
820
|
|
|
805
821
|
mg.show(locals())
|
|
806
822
|
```
|
|
807
|
-

|
|
808
824
|
|
|
809
825
|
## 3) Node_Key_Value ##
|
|
810
826
|
Node_Key_Value shows key-value pairs like a dictionary. Note the required `items()` call at the end.
|
|
@@ -828,7 +844,7 @@ mg.config.type_to_node[bintrees.avltree.Node] = lambda data: mg.Node_Key_Value(d
|
|
|
828
844
|
|
|
829
845
|
mg.show(locals())
|
|
830
846
|
```
|
|
831
|
-

|
|
832
848
|
|
|
833
849
|
## 4) Node_Table ##
|
|
834
850
|
Node_Table shows all the values as a table.
|
|
@@ -850,7 +866,7 @@ mg.config.type_to_node[bintrees.avltree.Node] = lambda data: mg.Node_Table(data,
|
|
|
850
866
|
|
|
851
867
|
mg.show(locals())
|
|
852
868
|
```
|
|
853
|
-

|
|
854
870
|
|
|
855
871
|
## Binary Search ##
|
|
856
872
|
For binary search we can use a List_View class to represent a particular sublist without making a list copy.
|
|
@@ -955,7 +971,7 @@ mg.config.type_to_depth[B] = 3
|
|
|
955
971
|
mg.config.type_to_depth[id(c)] = 2
|
|
956
972
|
mg.show(locals())
|
|
957
973
|
```
|
|
958
|
-

|
|
959
975
|
|
|
960
976
|
## Hidden Edges ##
|
|
961
977
|
|
|
@@ -971,7 +987,7 @@ for i in range(20):
|
|
|
971
987
|
|
|
972
988
|
mg.show(locals())
|
|
973
989
|
```
|
|
974
|
-

|
|
975
991
|
|
|
976
992
|
# Extensions #
|
|
977
993
|
Different extensions are available for types from other Python packages.
|
|
@@ -994,6 +1010,8 @@ mg.show(locals())
|
|
|
994
1010
|
```
|
|
995
1011
|

|
|
996
1012
|
|
|
1013
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#micropip=numpy&codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/mg_numpy.py&continues=1).
|
|
1014
|
+
|
|
997
1015
|
## Pandas ##
|
|
998
1016
|
Pandas types `Series` and `DataFrame` can be graphed with "memory_graph.extension_pandas":
|
|
999
1017
|
|
|
@@ -1013,6 +1031,8 @@ mg.show(locals())
|
|
|
1013
1031
|
```
|
|
1014
1032
|

|
|
1015
1033
|
|
|
1034
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#micropip=pandas&codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/mg_pandas.py&continues=1).
|
|
1035
|
+
|
|
1016
1036
|
## PyTorch ##
|
|
1017
1037
|
Torch type `tensor` can be graphed with "memory_graph.extension_torch":
|
|
1018
1038
|
|
|
@@ -1087,13 +1107,9 @@ $ bash create_gif.sh animated
|
|
|
1087
1107
|
```
|
|
1088
1108
|
|
|
1089
1109
|
# Troubleshooting #
|
|
1090
|
-
- 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 '
|
|
1110
|
+
- 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 'memory_graph.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 your default PDF reader. Another solution is to `render()` the graph to a different output format.
|
|
1091
1111
|
|
|
1092
1112
|
- When graph edges overlap it can be hard to distinguish them. Using an interactive graphviz viewer, such as [xdot](https://github.com/jrfonseca/xdot.py), on a '*.gv' DOT output file will help.
|
|
1093
1113
|
|
|
1094
|
-
# Social Media #
|
|
1095
|
-
* LinkedIn: https://www.linkedin.com/groups/13244150/
|
|
1096
|
-
* Reddit: https://www.reddit.com/r/Python_memory_graph/
|
|
1097
|
-
|
|
1098
1114
|
# Other Packages #
|
|
1099
1115
|
The [memory_graph](https://pypi.org/project/memory-graph/) package visualizes your data. If instead you want to visualize function calls, check out the [invocation_tree](https://pypi.org/project/invocation-tree/) package.
|
|
@@ -7,12 +7,16 @@ Additionally [Graphviz](https://graphviz.org/download/) needs to be installed.
|
|
|
7
7
|
|
|
8
8
|
# Highlights #
|
|
9
9
|

|
|
10
|
-
Run a live demo in the 👉 [**Memory Graph Web Debugger**](https://memory-graph.com/#play) 👈 now, no installation required!
|
|
10
|
+
Run a live demo in the 👉 [**Memory Graph Web Debugger**](https://memory-graph.com/#breakpoints=8&continues=1×tep=1.0&play) 👈 now, no installation required!
|
|
11
11
|
|
|
12
12
|
- learn the right **mental model** to think about Python data (references, mutability, shallow vs deep copy)
|
|
13
13
|
- **visualize the structure of your data** to easily understand and debug any data structure
|
|
14
14
|
- understand function calls, variable scope, and the **complete program state** through call stack visualization
|
|
15
15
|
|
|
16
|
+
An example Binary Tree data structure:
|
|
17
|
+

|
|
18
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/bin_tree.py×tep=0.2&play).
|
|
19
|
+
|
|
16
20
|
# Videos #
|
|
17
21
|
| [](https://www.youtube.com/watch?v=23_bHcr7hqo) | [](https://www.youtube.com/watch?v=pvIJgHCaXhU) |
|
|
18
22
|
|:--:|:--:|
|
|
@@ -87,7 +91,7 @@ identical?: True
|
|
|
87
91
|
```
|
|
88
92
|
A better way to understand what values are shared is to draw a graph using [memory_graph](https://pypi.org/project/memory-graph/).
|
|
89
93
|
|
|
90
|
-
#
|
|
94
|
+
# Topics #
|
|
91
95
|
|
|
92
96
|
[Python Data Model](#python-data-model)
|
|
93
97
|
|
|
@@ -119,8 +123,6 @@ A better way to understand what values are shared is to draw a graph using [memo
|
|
|
119
123
|
|
|
120
124
|
[Troubleshooting](#troubleshooting)
|
|
121
125
|
|
|
122
|
-
[Social Media](#social-media)
|
|
123
|
-
|
|
124
126
|
[Other Packages](#other-packages)
|
|
125
127
|
|
|
126
128
|
## Author ##
|
|
@@ -129,6 +131,10 @@ Bas Terwijn
|
|
|
129
131
|
## Inspiration ##
|
|
130
132
|
Inspired by [Python Tutor](https://pythontutor.com/).
|
|
131
133
|
|
|
134
|
+
## Social Media #
|
|
135
|
+
* [LinkedIn](https://www.linkedin.com/groups/13244150/)
|
|
136
|
+
* [Reddit](https://www.reddit.com/r/Python_memory_graph/)
|
|
137
|
+
|
|
132
138
|
## Supported by ##
|
|
133
139
|
<img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/uva.png" alt="University of Amsterdam" width="600">
|
|
134
140
|
|
|
@@ -229,6 +235,8 @@ mg.show(locals())
|
|
|
229
235
|
```
|
|
230
236
|

|
|
231
237
|
|
|
238
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/custom_copy.py&breakpoints=15&continues=1&play).
|
|
239
|
+
|
|
232
240
|
## Name Rebinding ##
|
|
233
241
|
When `a` and `b` share a mutable value, then changing the value of `b` changes the value of `a` and vice versa. However, reassigning `b` does not change `a`. When you reassign `b`, you only rebind the name `b` to another value without effecting any other variables.
|
|
234
242
|
|
|
@@ -303,6 +311,8 @@ print(f"a:{a} b:{b} c:{c}")
|
|
|
303
311
|
```
|
|
304
312
|

|
|
305
313
|
|
|
314
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/function_call.py&play).
|
|
315
|
+
|
|
306
316
|
In the printed output only `a` is changed as a result of the function call:
|
|
307
317
|
```
|
|
308
318
|
a:[4, 3, 2, 1] b:(4, 3, 2) c:[4, 3, 2]
|
|
@@ -310,6 +320,8 @@ a:[4, 3, 2, 1] b:(4, 3, 2) c:[4, 3, 2]
|
|
|
310
320
|
|
|
311
321
|
This is because `b` is of immutable type 'tuple' so its value gets copied automatically when it is changed. And because the function is called with a copy of `c`, its original value is not changed by the function. The value of variable `a` is the only value of mutable type that is shared between the root stack frame **'0: \<module>'** and the **'1: add_one'** stack frame of the function so only that variable is affected as a result of the function call. The other changes remain confined to the local variables of the ```add_one()``` function.
|
|
312
322
|
|
|
323
|
+
Now is a good time to practice the Python Data Model. Here are [some exercises](https://github.com/bterwijn/memory_graph_videos/blob/main/exercises/exercises.md) on references, mutability, copies, and function calls.
|
|
324
|
+
|
|
313
325
|
## Block ##
|
|
314
326
|
It is often helpful to temporarily block program execution to inspect the graph. For this we can use the `mg.block()` function:
|
|
315
327
|
|
|
@@ -339,16 +351,17 @@ def factorial(n):
|
|
|
339
351
|
|
|
340
352
|
print( factorial(4) )
|
|
341
353
|
```
|
|
342
|
-
|
|
343
354
|

|
|
344
355
|
|
|
345
356
|
and the result is: 1 x 2 x 3 x 4 = 24
|
|
346
357
|
|
|
347
|
-
|
|
348
|
-
|
|
358
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/factorial.py×tep=1.0&play).
|
|
359
|
+
|
|
360
|
+
## Binary Conversion ##
|
|
361
|
+
A more interesting recursive example is function `binary()` that converts a integer from decimal to binary representation.
|
|
349
362
|
```python
|
|
350
363
|
import memory_graph as mg
|
|
351
|
-
mg.config.
|
|
364
|
+
mg.config.type_to_horizontal[list] = True # horizontal lists
|
|
352
365
|
|
|
353
366
|
def binary(value: int) -> list[int]:
|
|
354
367
|
mg.block(mg.show(), mg.stack())
|
|
@@ -363,9 +376,11 @@ print( binary(100) )
|
|
|
363
376
|
```
|
|
364
377
|

|
|
365
378
|
```
|
|
366
|
-
|
|
379
|
+
[1, 1, 0, 0, 1, 0, 0]
|
|
367
380
|
```
|
|
368
381
|
|
|
382
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/binary_convert.py×tep=1.0&play).
|
|
383
|
+
|
|
369
384
|
## Power Set ##
|
|
370
385
|
A more complex recursive example is function `power_set()` where lists are shared by different function calls. A power set is the set of all subsets of a collection of values.
|
|
371
386
|
|
|
@@ -396,6 +411,8 @@ print( power_set(['a', 'b', 'c']) )
|
|
|
396
411
|
[['a', 'b', 'c'], ['a', 'b'], ['a', 'c'], ['a'], ['b', 'c'], ['b'], ['c'], []]
|
|
397
412
|
```
|
|
398
413
|
|
|
414
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/power_set.py×tep=1.0&play).
|
|
415
|
+
|
|
399
416
|
# Debugging #
|
|
400
417
|
|
|
401
418
|
For the best debugging experience with memory_graph set for example expression:
|
|
@@ -594,10 +611,10 @@ for i in range(n):
|
|
|
594
611
|
Here we show values being inserted in a HashSet in PyCharm. When inserting the last value '44' we "Step Into" the code to show more of the details.
|
|
595
612
|

|
|
596
613
|
|
|
597
|
-
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/hash_set.py×tep=0.
|
|
614
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/hash_set.py&breakpoints=32&continues=1×tep=0.5&play).
|
|
598
615
|
|
|
599
616
|
# Configuration #
|
|
600
|
-
Different aspects of memory_graph can be configured. The default configuration can be reset by calling 'mg.config_default.reset()'.
|
|
617
|
+
Different aspects of memory_graph can be configured. The default configuration can be reset by calling 'mg.config_default.reset()'. The Memory Graph Web Debugger gives examples of the [most important configurations](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/config.py&play).
|
|
601
618
|
|
|
602
619
|
- ***mg.config.reopen_viewer*** : bool
|
|
603
620
|
- If True the viewer is reopened each time show() is called, this might change window focus, default True.
|
|
@@ -632,8 +649,8 @@ Different aspects of memory_graph can be configured. The default configuration c
|
|
|
632
649
|
- ***mg.config.type_to_color*** : dict[type, color]
|
|
633
650
|
- Maps a type to the [graphviz color](https://graphviz.org/doc/info/colors.html) it gets in the graph.
|
|
634
651
|
|
|
635
|
-
- ***mg.config.
|
|
636
|
-
- Maps a type to its orientation. Use 'True' for
|
|
652
|
+
- ***mg.config.type_to_horizontal*** : dict[type, bool]
|
|
653
|
+
- Maps a type to its orientation for Node_Linear and Node_Key_Value. Use 'True' for horizontal and 'False' for vertical. If not specified these nodes vertical unless they have references to children.
|
|
637
654
|
|
|
638
655
|
- ***mg.config.type_to_slicer*** : dict[type, int]
|
|
639
656
|
- Maps a type to a Slicer. A slicer determines how many elements of a data type are shown in the graph to prevent the graph from getting too big. 'Slicer()' does no slicing, 'Slicer(1,2,3)' shows just 1 element at the beginning, 2 in the middle, and 3 at the end.
|
|
@@ -690,7 +707,6 @@ mg.show(locals())
|
|
|
690
707
|
```
|
|
691
708
|

|
|
692
709
|
|
|
693
|
-
|
|
694
710
|
## All attributes using dir() ##
|
|
695
711
|
A useful start is to give it some color, show the list of all its attributes using `dir()`, and setting an empty Slicer to see the attribute list in full.
|
|
696
712
|
|
|
@@ -712,7 +728,7 @@ mg.config.type_to_slicer[bintrees.avltree.Node] = mg.Slicer()
|
|
|
712
728
|
|
|
713
729
|
mg.show(locals())
|
|
714
730
|
```
|
|
715
|
-

|
|
716
732
|
|
|
717
733
|
Next figure out what the attributes are you want to graph and choose a Node type, there are four options:
|
|
718
734
|
|
|
@@ -735,7 +751,7 @@ mg.config.type_to_node[bintrees.avltree.Node] = lambda data: mg.Node_Leaf(data,
|
|
|
735
751
|
|
|
736
752
|
mg.show(locals())
|
|
737
753
|
```
|
|
738
|
-

|
|
739
755
|
|
|
740
756
|
## 2) Node_Linear ##
|
|
741
757
|
Node_Linear shows multiple values in a line like a list.
|
|
@@ -759,7 +775,7 @@ mg.config.type_to_node[bintrees.avltree.Node] = lambda data: mg.Node_Linear(data
|
|
|
759
775
|
|
|
760
776
|
mg.show(locals())
|
|
761
777
|
```
|
|
762
|
-

|
|
763
779
|
|
|
764
780
|
## 3) Node_Key_Value ##
|
|
765
781
|
Node_Key_Value shows key-value pairs like a dictionary. Note the required `items()` call at the end.
|
|
@@ -783,7 +799,7 @@ mg.config.type_to_node[bintrees.avltree.Node] = lambda data: mg.Node_Key_Value(d
|
|
|
783
799
|
|
|
784
800
|
mg.show(locals())
|
|
785
801
|
```
|
|
786
|
-

|
|
787
803
|
|
|
788
804
|
## 4) Node_Table ##
|
|
789
805
|
Node_Table shows all the values as a table.
|
|
@@ -805,7 +821,7 @@ mg.config.type_to_node[bintrees.avltree.Node] = lambda data: mg.Node_Table(data,
|
|
|
805
821
|
|
|
806
822
|
mg.show(locals())
|
|
807
823
|
```
|
|
808
|
-

|
|
809
825
|
|
|
810
826
|
## Binary Search ##
|
|
811
827
|
For binary search we can use a List_View class to represent a particular sublist without making a list copy.
|
|
@@ -910,7 +926,7 @@ mg.config.type_to_depth[B] = 3
|
|
|
910
926
|
mg.config.type_to_depth[id(c)] = 2
|
|
911
927
|
mg.show(locals())
|
|
912
928
|
```
|
|
913
|
-

|
|
914
930
|
|
|
915
931
|
## Hidden Edges ##
|
|
916
932
|
|
|
@@ -926,7 +942,7 @@ for i in range(20):
|
|
|
926
942
|
|
|
927
943
|
mg.show(locals())
|
|
928
944
|
```
|
|
929
|
-

|
|
930
946
|
|
|
931
947
|
# Extensions #
|
|
932
948
|
Different extensions are available for types from other Python packages.
|
|
@@ -949,6 +965,8 @@ mg.show(locals())
|
|
|
949
965
|
```
|
|
950
966
|

|
|
951
967
|
|
|
968
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#micropip=numpy&codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/mg_numpy.py&continues=1).
|
|
969
|
+
|
|
952
970
|
## Pandas ##
|
|
953
971
|
Pandas types `Series` and `DataFrame` can be graphed with "memory_graph.extension_pandas":
|
|
954
972
|
|
|
@@ -968,6 +986,8 @@ mg.show(locals())
|
|
|
968
986
|
```
|
|
969
987
|

|
|
970
988
|
|
|
989
|
+
Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#micropip=pandas&codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/mg_pandas.py&continues=1).
|
|
990
|
+
|
|
971
991
|
## PyTorch ##
|
|
972
992
|
Torch type `tensor` can be graphed with "memory_graph.extension_torch":
|
|
973
993
|
|
|
@@ -1042,13 +1062,9 @@ $ bash create_gif.sh animated
|
|
|
1042
1062
|
```
|
|
1043
1063
|
|
|
1044
1064
|
# Troubleshooting #
|
|
1045
|
-
- 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 '
|
|
1065
|
+
- 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 'memory_graph.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 your default PDF reader. Another solution is to `render()` the graph to a different output format.
|
|
1046
1066
|
|
|
1047
1067
|
- When graph edges overlap it can be hard to distinguish them. Using an interactive graphviz viewer, such as [xdot](https://github.com/jrfonseca/xdot.py), on a '*.gv' DOT output file will help.
|
|
1048
1068
|
|
|
1049
|
-
# Social Media #
|
|
1050
|
-
* LinkedIn: https://www.linkedin.com/groups/13244150/
|
|
1051
|
-
* Reddit: https://www.reddit.com/r/Python_memory_graph/
|
|
1052
|
-
|
|
1053
1069
|
# Other Packages #
|
|
1054
1070
|
The [memory_graph](https://pypi.org/project/memory-graph/) package visualizes your data. If instead you want to visualize function calls, check out the [invocation_tree](https://pypi.org/project/invocation-tree/) package.
|
|
@@ -103,10 +103,12 @@ def reset():
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
""" Types that will be visualized in vertical orientation
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
106
|
+
""" Types that will be visualized in horizontal or vertical orientation based on a True/False value.
|
|
107
|
+
The 'type_to_horizontal' takes precedence over 'type_to_vertical'.
|
|
108
|
+
If no boolean value is present the Node decides based on it having references."""
|
|
109
|
+
config.type_to_horizontal = {}
|
|
110
|
+
config.type_to_vertical = {}
|
|
111
|
+
|
|
110
112
|
|
|
111
113
|
""" Slicer objects for different types. """
|
|
112
114
|
config.type_to_slicer = {
|
|
@@ -23,10 +23,17 @@ def get_color(node, default='white'):
|
|
|
23
23
|
default)
|
|
24
24
|
|
|
25
25
|
def get_vertical(node, default):
|
|
26
|
+
horizontal = get_property(node.get_id(),
|
|
27
|
+
node.get_type(),
|
|
28
|
+
type(node),
|
|
29
|
+
config.type_to_horizontal,
|
|
30
|
+
None)
|
|
31
|
+
if isinstance(horizontal, bool):
|
|
32
|
+
return not horizontal
|
|
26
33
|
return get_property(node.get_id(),
|
|
27
34
|
node.get_type(),
|
|
28
35
|
type(node),
|
|
29
|
-
config.type_to_vertical,
|
|
36
|
+
config.type_to_vertical,
|
|
30
37
|
default)
|
|
31
38
|
|
|
32
39
|
def get_slicer(node, data, default=Slicer(3,2,3)):
|