memory-graph 0.3.65__tar.gz → 0.3.67__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.
Files changed (44) hide show
  1. {memory_graph-0.3.65 → memory_graph-0.3.67}/PKG-INFO +38 -29
  2. {memory_graph-0.3.65 → memory_graph-0.3.67}/README.md +37 -28
  3. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/__init__.py +125 -1
  4. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/config.py +1 -1
  5. memory_graph-0.3.67/memory_graph/extension_numpy.py +48 -0
  6. memory_graph-0.3.67/memory_graph/extension_pandas.py +36 -0
  7. memory_graph-0.3.67/memory_graph/extension_torch.py +19 -0
  8. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph.egg-info/PKG-INFO +38 -29
  9. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph.egg-info/SOURCES.txt +0 -1
  10. {memory_graph-0.3.65 → memory_graph-0.3.67}/pyproject.toml +1 -1
  11. memory_graph-0.3.65/memory_graph/extension_numpy.py +0 -33
  12. memory_graph-0.3.65/memory_graph/extension_pandas.py +0 -26
  13. memory_graph-0.3.65/memory_graph/extension_torch.py +0 -6
  14. memory_graph-0.3.65/setup.py +0 -7
  15. {memory_graph-0.3.65 → memory_graph-0.3.67}/LICENSE.txt +0 -0
  16. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/call_stack.py +0 -0
  17. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/config_default.py +0 -0
  18. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/config_helpers.py +0 -0
  19. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/html_table.py +0 -0
  20. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/list_view.py +0 -0
  21. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/memory_to_nodes.py +0 -0
  22. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/node_base.py +0 -0
  23. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/node_key_value.py +0 -0
  24. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/node_leaf.py +0 -0
  25. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/node_linear.py +0 -0
  26. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/node_table.py +0 -0
  27. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/sequence.py +0 -0
  28. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/slicer.py +0 -0
  29. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/slices.py +0 -0
  30. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/slices_iterator.py +0 -0
  31. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/slices_table_iterator.py +0 -0
  32. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/test.py +0 -0
  33. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/test_max_graph_depth.py +0 -0
  34. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/test_memory_graph.py +0 -0
  35. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/test_memory_to_nodes.py +0 -0
  36. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/test_sequence.py +0 -0
  37. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/test_slicer.py +0 -0
  38. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/test_slices.py +0 -0
  39. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/test_slices_iterator.py +0 -0
  40. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph/utils.py +0 -0
  41. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph.egg-info/dependency_links.txt +0 -0
  42. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph.egg-info/requires.txt +0 -0
  43. {memory_graph-0.3.65 → memory_graph-0.3.67}/memory_graph.egg-info/top_level.txt +0 -0
  44. {memory_graph-0.3.65 → memory_graph-0.3.67}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memory_graph
3
- Version: 0.3.65
3
+ Version: 0.3.67
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-Expression: BSD-2-Clause
@@ -30,11 +30,11 @@ Additionally [Graphviz](https://graphviz.org/download/) needs to be installed.
30
30
  Run a live demo in the 👉 [**Memory Graph Web Debugger**](https://memory-graph.com/#breakpoints=8&continues=1&timestep=1.0&play) 👈 now, no installation required!
31
31
 
32
32
  - learn the right **mental model** to think about Python data (references, mutability, shallow vs deep copy)
33
- - **visualize the structure of your data** to easily understand and debug any data structure
33
+ - **visualize the structure of your data** to more easily understand and debug any data structure
34
34
  - understand function calls, variable scope, and the **complete program state** through call stack visualization
35
35
 
36
36
  An example Binary Tree data structure:
37
- ![images/bin_tree.gif](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_tree.gif)
37
+ ![images/bin_tree_vs.gif](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_tree_vs.gif)
38
38
  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&timestep=0.2&play).
39
39
 
40
40
  # Videos #
@@ -43,7 +43,7 @@ Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=ht
43
43
  | [Quick Intro (3:49)](https://www.youtube.com/watch?v=23_bHcr7hqo) | [Mutability (17:29)](https://www.youtube.com/watch?v=pvIJgHCaXhU) |
44
44
 
45
45
  # Memory Graph #
46
- For program understanding and debugging, the [memory_graph](https://pypi.org/project/memory-graph/) package can visualize your data, supporting many different data types, including but not limited to:
46
+ For program understanding and debugging, the [memory_graph](https://github.com/bterwijn/memory_graph) package can visualize your data, supporting many different data types, including but not limited to:
47
47
 
48
48
  ```python
49
49
  import memory_graph as mg
@@ -69,7 +69,7 @@ mg.render(data, "my_graph.gv") # Graphviz DOT file
69
69
  mg.render(data) # renders to default: 'memory_graph.pdf'
70
70
  ```
71
71
 
72
- # Sharing Values #
72
+ # Sharing Values, Aliasing #
73
73
  In Python, assigning a list from variable `a` to variable `b` causes both variables to reference the same list value and thus share it. Consequently, any change applied through one variable will impact the other. This behavior can lead to elusive bugs if a programmer incorrectly assumes that list `a` and `b` are independent.
74
74
 
75
75
  <table><tr><td>
@@ -86,7 +86,7 @@ b.append(1) # changing 'b' changes 'a'
86
86
  print('a:', a)
87
87
  print('b:', b)
88
88
 
89
- # check if 'a' and 'b' share values
89
+ # check if 'a' and 'b' share the list
90
90
  print('ids:', id(a), id(b))
91
91
  print('identical?:', a is b)
92
92
 
@@ -98,18 +98,18 @@ mg.show( locals() )
98
98
 
99
99
  ![mutable2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/mutable2.png)
100
100
 
101
- a graph showing `a` and `b` share values
101
+ a graph showing `a` and `b` share the list
102
102
 
103
103
  </td></tr></table>
104
104
 
105
- The fact that `a` and `b` share values can not be verified by printing the lists. It can be verified by comparing the identity of both variables using the `id()` function or by using the `is` comparison operator as shown in the program output below, but this quickly becomes impractical for larger programs.
105
+ The fact that `a` and `b` share the list can not be verified by printing the lists. It can be verified by comparing the identity of both variables using the `id()` function or by using the `is` comparison operator as shown in the program output below, but this quickly becomes impractical for larger programs.
106
106
  ```{verbatim}
107
107
  a: 4, 3, 2, 1
108
108
  b: 4, 3, 2, 1
109
109
  ids: 126432214913216 126432214913216
110
110
  identical?: True
111
111
  ```
112
- A better way to understand what values are shared is to draw a graph using [memory_graph](https://pypi.org/project/memory-graph/).
112
+ A better way to understand what values are shared is to draw a graph using [memory_graph](https://github.com/bterwijn/memory_graph).
113
113
 
114
114
  # Topics #
115
115
 
@@ -161,7 +161,7 @@ Bas Terwijn
161
161
  ## Inspiration ##
162
162
  Inspired by [Python Tutor](https://pythontutor.com/).
163
163
 
164
- The main differences are that running memory_graph locally is a key design choice to support Python Tutor’s [unsupported features](https://github.com/pythontutor-dev/pythontutor/blob/master/unsupported-features.md#unsupported-features) and mirroring the data’s hierarchy improves graph readability.
164
+ The main differences are that by running memory_graph locally we support Python Tutor’s [unsupported features](https://github.com/pythontutor-dev/pythontutor/blob/master/unsupported-features.md#unsupported-features) so that it scales to full programs in many environments and IDEs instead of just code snippets in a webbrowser, and by mirroring the data’s hierarchy we improve graph readability for larger graphs.
165
165
 
166
166
  ## Social Media #
167
167
  * [LinkedIn](https://www.linkedin.com/groups/13244150/)
@@ -271,22 +271,28 @@ mg.show(locals())
271
271
  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).
272
272
 
273
273
  ## Name Rebinding ##
274
- 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 affecting any other variable.
274
+ 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 affecting any other variable.
275
+
276
+ Also note the difference between statement `b += [1]` that changes `b` and `a`, and statement `c = c + [300]` that first creates the new value `c + [300]` and assigns this value to `c` without effecting `b`. This shows that `x += y` is not the same as `x = x + y` for values of mutable type.
275
277
 
276
278
  ```python
277
279
  import memory_graph as mg
278
280
 
279
- a = [4, 3, 2]
281
+ a = [100, 200]
280
282
  b = a
281
283
  mg.render(locals(), 'rebinding1.png')
282
284
 
283
- b += [1] # changes the value of 'b' and 'a'
284
- b = [100, 200] # rebinds 'b' to another value, 'a' is unaffected
285
+ b += [300] # changes the value of 'b' and 'a'
286
+ b = [400, 500] # rebinds 'b' to a new value, 'a' is unaffected
287
+ c = b
285
288
  mg.render(locals(), 'rebinding2.png')
289
+
290
+ c = c + [600] # rebinds 'c' to new value 'c + [600]', `b` is unaffected
291
+ mg.render(locals(), 'rebinding3.png')
286
292
  ```
287
- | ![rebinding1.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding1.png) | ![rebinding2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding2.png) |
288
- |:-----------------------------------------------------------:|:-------------------------------------------------------------:|
289
- | rebinding1.png | rebinding2.png |
293
+ | ![rebinding1.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding1.png) | ![rebinding2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding2.png) | ![rebinding3.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding3.png) |
294
+ |:--------------:|:--------------:|:--------------:|
295
+ | rebinding1.png | rebinding2.png | rebinding3.png |
290
296
 
291
297
  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/rebind.py&play).
292
298
 
@@ -314,7 +320,7 @@ When copying a mix of values of mutable and immutable type, to save on time and
314
320
  import memory_graph as mg
315
321
  import copy
316
322
 
317
- a = ( [1, 2], ('x', 'y') ) # mix of mutable and immutable
323
+ a = ( [1, 2], ('x', 'y') ) # mix of mutable and immutable values
318
324
 
319
325
  # three different ways to make a "copy" of 'a':
320
326
  c1 = a
@@ -381,7 +387,7 @@ The effect of calling `add_one()` is that `b[0]` increases by 1, while `a` is un
381
387
 
382
388
  # Data Model Exercises #
383
389
 
384
- Now is a good time to practice with Python Data Model concepts. Here are [some exercises](https://github.com/bterwijn/memory_graph_videos/blob/main/exercises/exercises.md) on references, mutability, copies, and function calls.
390
+ Now is a good time to practice with these Python Data Model concepts. Here are [some exercises](https://github.com/bterwijn/memory_graph_videos/blob/main/exercises/exercises.md) on references, mutability, copies, and function calls. Also see the programming exercises at [the end of the Mutability video](https://www.youtube.com/watch?v=pvIJgHCaXhU&t=891s).
385
391
 
386
392
  # Block #
387
393
  It is often helpful to temporarily block program execution to inspect the graph. For this we can use the `mg.block()` function:
@@ -394,7 +400,7 @@ This function:
394
400
  * first executes `fun(arg1, arg2, ...)`
395
401
  * then prints the current source location in the program
396
402
  * then blocks execution until the &lt;Enter&gt; key is pressed
397
- * finally returns the return value of the `fun()` call
403
+ * and returns the return value of the `fun()` call
398
404
 
399
405
  ## Recursion ##
400
406
  The call stack is also helpful to visualize how recursion works. Here we use `mg.block()` to show each step of how recursively `factorial(4)` is computed:
@@ -494,6 +500,7 @@ The ```mg.stack()``` doesn't work well in **watch** context in most debuggers be
494
500
  |:---|:---|
495
501
  | [pdb](https://docs.python.org/3/library/pdb.html), [pudb](https://pypi.org/project/pudb/) | `mg.stack_pdb()` |
496
502
  | [Visual Studio Code](https://code.visualstudio.com/docs/languages/python) | `mg.stack_vscode()` |
503
+ | [Jupyter Notebooks in VS Code](https://code.visualstudio.com/docs/datascience/jupyter-notebooks) | `mg.stack_vscode_jupyter()` |
497
504
  | [Cursor AI](https://www.cursor.com/) | `mg.stack_cursor()` |
498
505
  | [PyCharm](https://www.jetbrains.com/pycharm/) | `mg.stack_pycharm()` |
499
506
  | [Wing](https://wingware.com/) | `mg.stack_wing()` |
@@ -775,7 +782,7 @@ Different aspects of memory_graph can be configured. The default configuration c
775
782
  ## Simplified Graph ##
776
783
  Memory_graph simplifies the visualization (and the viewer's mental model) by **not** showing separate nodes for immutable types like `bool`, `int`, `float`, `complex`, and `str` by default. This simplification can sometimes be slightly misleading. As in the example below, after a shallow copy, lists `a` and `b` technically share their `int` values, but the graph makes it appear as though `a` and `b` each have their own copies. However, since `int` is immutable, this simplification will never lead to unexpected changes (changing `a`'s ints won’t affect `b`) so will never result in bugs.
777
784
 
778
- The simplification strikes a balance: it is slightly misleading but keeps the graph clean and easy to understand and focuses on the mutable types where unexpected changes can occur. This is why it is the default behavior. If you do want to show separate nodes for `int` values, such as for educational purposes, you can simply remove `int` from the `mg.config.embedded_types` set:
785
+ The simplification strikes a balance: it is slightly misleading but keeps the graph clean and easy to understand to focus on mutable types where unexpected changes can occur. This is why it is the default behavior. If you do want to show separate nodes for `int` values, such as for educational purposes, you can simply remove `int` from the `mg.config.embedded_types` set:
779
786
  ```python
780
787
  import memory_graph as mg
781
788
 
@@ -809,7 +816,7 @@ tree.insert(15, "fifteen")
809
816
 
810
817
  mg.show(locals())
811
818
  ```
812
- ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_fail.png)
819
+ ![avltree_fail.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_fail.png)
813
820
 
814
821
  ## All attributes using dir() ##
815
822
  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.
@@ -980,6 +987,8 @@ mg.config.type_to_node[List_View] = (lambda l: mg.Node_Linear(l,
980
987
  ```
981
988
  ![bin_search_linear.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_search_linear.png)
982
989
 
990
+ 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_search.py&breakpoints=32&continues=1&timestep=0.5&play)
991
+
983
992
  # Graph Depth #
984
993
  To limit the size of the graph the maximum depth of the graph is set by `mg.config.max_graph_depth`. Additionally for each type a depth can be set to further limit the graph, as is done for type `B` in the example below. Scissors indicate where the graph is cut short. Alternatively the `id()` of a data elements can be used to limit the graph for that specific element, as is done for the value referenced by variable `c`.
985
994
 
@@ -1052,12 +1061,12 @@ mg.show(locals())
1052
1061
  Different extensions are available for types from other Python packages.
1053
1062
 
1054
1063
  ## Numpy ##
1055
- Numpy types `array` and `matrix` and `ndarray` can be graphed with "memory_graph.extension_numpy":
1064
+ For Numpy types `array` and `matrix` and `ndarray`, use `mg.extend_numpy()`:
1056
1065
 
1057
1066
  ```python
1058
1067
  import memory_graph as mg
1059
1068
  import numpy as np
1060
- import memory_graph.extension_numpy
1069
+ mg.extend_numpy()
1061
1070
  np.random.seed(0) # use same random numbers each run
1062
1071
 
1063
1072
  matrix = np.matrix([[i*5+j for j in range(4)] for i in range(5)])
@@ -1072,12 +1081,12 @@ mg.show(locals())
1072
1081
  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).
1073
1082
 
1074
1083
  ## Pandas ##
1075
- Pandas types `Series` and `DataFrame` can be graphed with "memory_graph.extension_pandas":
1084
+ For pandas types `Series` and `DataFrame`, use `mg.extend_pandas()`:
1076
1085
 
1077
1086
  ```python
1078
1087
  import memory_graph as mg
1079
1088
  import pandas as pd
1080
- import memory_graph.extension_pandas
1089
+ mg.extend_pandas()
1081
1090
 
1082
1091
  series = pd.Series( [i for i in range(20)] )
1083
1092
  dataframe1 = pd.DataFrame({ "calories": [420, 380, 390],
@@ -1093,12 +1102,12 @@ mg.show(locals())
1093
1102
  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).
1094
1103
 
1095
1104
  ## PyTorch ##
1096
- Torch type `tensor` can be graphed with "memory_graph.extension_torch":
1105
+ For torch type `tensor`, use `mg.extend_torch()`:
1097
1106
 
1098
1107
  ```python
1099
1108
  import memory_graph as mg
1100
1109
  import torch
1101
- import memory_graph.extension_torch
1110
+ mg.extend_torch()
1102
1111
  torch.manual_seed(0) # same random numbers each run
1103
1112
 
1104
1113
  tensor_1d = torch.rand(3)
@@ -1171,4 +1180,4 @@ $ bash create_gif.sh animated
1171
1180
  - 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.
1172
1181
 
1173
1182
  # Other Packages #
1174
- 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.
1183
+ The [memory_graph](https://github.com/bterwijn/memory_graph) package visualizes your data. If instead you want to visualize function calls, check out the [invocation_tree](https://github.com/bterwijn/invocation_tree) package.
@@ -10,11 +10,11 @@ Additionally [Graphviz](https://graphviz.org/download/) needs to be installed.
10
10
  Run a live demo in the 👉 [**Memory Graph Web Debugger**](https://memory-graph.com/#breakpoints=8&continues=1&timestep=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
- - **visualize the structure of your data** to easily understand and debug any data structure
13
+ - **visualize the structure of your data** to more 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
16
  An example Binary Tree data structure:
17
- ![images/bin_tree.gif](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_tree.gif)
17
+ ![images/bin_tree_vs.gif](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_tree_vs.gif)
18
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&timestep=0.2&play).
19
19
 
20
20
  # Videos #
@@ -23,7 +23,7 @@ Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=ht
23
23
  | [Quick Intro (3:49)](https://www.youtube.com/watch?v=23_bHcr7hqo) | [Mutability (17:29)](https://www.youtube.com/watch?v=pvIJgHCaXhU) |
24
24
 
25
25
  # Memory Graph #
26
- For program understanding and debugging, the [memory_graph](https://pypi.org/project/memory-graph/) package can visualize your data, supporting many different data types, including but not limited to:
26
+ For program understanding and debugging, the [memory_graph](https://github.com/bterwijn/memory_graph) package can visualize your data, supporting many different data types, including but not limited to:
27
27
 
28
28
  ```python
29
29
  import memory_graph as mg
@@ -49,7 +49,7 @@ mg.render(data, "my_graph.gv") # Graphviz DOT file
49
49
  mg.render(data) # renders to default: 'memory_graph.pdf'
50
50
  ```
51
51
 
52
- # Sharing Values #
52
+ # Sharing Values, Aliasing #
53
53
  In Python, assigning a list from variable `a` to variable `b` causes both variables to reference the same list value and thus share it. Consequently, any change applied through one variable will impact the other. This behavior can lead to elusive bugs if a programmer incorrectly assumes that list `a` and `b` are independent.
54
54
 
55
55
  <table><tr><td>
@@ -66,7 +66,7 @@ b.append(1) # changing 'b' changes 'a'
66
66
  print('a:', a)
67
67
  print('b:', b)
68
68
 
69
- # check if 'a' and 'b' share values
69
+ # check if 'a' and 'b' share the list
70
70
  print('ids:', id(a), id(b))
71
71
  print('identical?:', a is b)
72
72
 
@@ -78,18 +78,18 @@ mg.show( locals() )
78
78
 
79
79
  ![mutable2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/mutable2.png)
80
80
 
81
- a graph showing `a` and `b` share values
81
+ a graph showing `a` and `b` share the list
82
82
 
83
83
  </td></tr></table>
84
84
 
85
- The fact that `a` and `b` share values can not be verified by printing the lists. It can be verified by comparing the identity of both variables using the `id()` function or by using the `is` comparison operator as shown in the program output below, but this quickly becomes impractical for larger programs.
85
+ The fact that `a` and `b` share the list can not be verified by printing the lists. It can be verified by comparing the identity of both variables using the `id()` function or by using the `is` comparison operator as shown in the program output below, but this quickly becomes impractical for larger programs.
86
86
  ```{verbatim}
87
87
  a: 4, 3, 2, 1
88
88
  b: 4, 3, 2, 1
89
89
  ids: 126432214913216 126432214913216
90
90
  identical?: True
91
91
  ```
92
- A better way to understand what values are shared is to draw a graph using [memory_graph](https://pypi.org/project/memory-graph/).
92
+ A better way to understand what values are shared is to draw a graph using [memory_graph](https://github.com/bterwijn/memory_graph).
93
93
 
94
94
  # Topics #
95
95
 
@@ -141,7 +141,7 @@ Bas Terwijn
141
141
  ## Inspiration ##
142
142
  Inspired by [Python Tutor](https://pythontutor.com/).
143
143
 
144
- The main differences are that running memory_graph locally is a key design choice to support Python Tutor’s [unsupported features](https://github.com/pythontutor-dev/pythontutor/blob/master/unsupported-features.md#unsupported-features) and mirroring the data’s hierarchy improves graph readability.
144
+ The main differences are that by running memory_graph locally we support Python Tutor’s [unsupported features](https://github.com/pythontutor-dev/pythontutor/blob/master/unsupported-features.md#unsupported-features) so that it scales to full programs in many environments and IDEs instead of just code snippets in a webbrowser, and by mirroring the data’s hierarchy we improve graph readability for larger graphs.
145
145
 
146
146
  ## Social Media #
147
147
  * [LinkedIn](https://www.linkedin.com/groups/13244150/)
@@ -251,22 +251,28 @@ mg.show(locals())
251
251
  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).
252
252
 
253
253
  ## Name Rebinding ##
254
- 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 affecting any other variable.
254
+ 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 affecting any other variable.
255
+
256
+ Also note the difference between statement `b += [1]` that changes `b` and `a`, and statement `c = c + [300]` that first creates the new value `c + [300]` and assigns this value to `c` without effecting `b`. This shows that `x += y` is not the same as `x = x + y` for values of mutable type.
255
257
 
256
258
  ```python
257
259
  import memory_graph as mg
258
260
 
259
- a = [4, 3, 2]
261
+ a = [100, 200]
260
262
  b = a
261
263
  mg.render(locals(), 'rebinding1.png')
262
264
 
263
- b += [1] # changes the value of 'b' and 'a'
264
- b = [100, 200] # rebinds 'b' to another value, 'a' is unaffected
265
+ b += [300] # changes the value of 'b' and 'a'
266
+ b = [400, 500] # rebinds 'b' to a new value, 'a' is unaffected
267
+ c = b
265
268
  mg.render(locals(), 'rebinding2.png')
269
+
270
+ c = c + [600] # rebinds 'c' to new value 'c + [600]', `b` is unaffected
271
+ mg.render(locals(), 'rebinding3.png')
266
272
  ```
267
- | ![rebinding1.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding1.png) | ![rebinding2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding2.png) |
268
- |:-----------------------------------------------------------:|:-------------------------------------------------------------:|
269
- | rebinding1.png | rebinding2.png |
273
+ | ![rebinding1.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding1.png) | ![rebinding2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding2.png) | ![rebinding3.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding3.png) |
274
+ |:--------------:|:--------------:|:--------------:|
275
+ | rebinding1.png | rebinding2.png | rebinding3.png |
270
276
 
271
277
  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/rebind.py&play).
272
278
 
@@ -294,7 +300,7 @@ When copying a mix of values of mutable and immutable type, to save on time and
294
300
  import memory_graph as mg
295
301
  import copy
296
302
 
297
- a = ( [1, 2], ('x', 'y') ) # mix of mutable and immutable
303
+ a = ( [1, 2], ('x', 'y') ) # mix of mutable and immutable values
298
304
 
299
305
  # three different ways to make a "copy" of 'a':
300
306
  c1 = a
@@ -361,7 +367,7 @@ The effect of calling `add_one()` is that `b[0]` increases by 1, while `a` is un
361
367
 
362
368
  # Data Model Exercises #
363
369
 
364
- Now is a good time to practice with Python Data Model concepts. Here are [some exercises](https://github.com/bterwijn/memory_graph_videos/blob/main/exercises/exercises.md) on references, mutability, copies, and function calls.
370
+ Now is a good time to practice with these Python Data Model concepts. Here are [some exercises](https://github.com/bterwijn/memory_graph_videos/blob/main/exercises/exercises.md) on references, mutability, copies, and function calls. Also see the programming exercises at [the end of the Mutability video](https://www.youtube.com/watch?v=pvIJgHCaXhU&t=891s).
365
371
 
366
372
  # Block #
367
373
  It is often helpful to temporarily block program execution to inspect the graph. For this we can use the `mg.block()` function:
@@ -374,7 +380,7 @@ This function:
374
380
  * first executes `fun(arg1, arg2, ...)`
375
381
  * then prints the current source location in the program
376
382
  * then blocks execution until the &lt;Enter&gt; key is pressed
377
- * finally returns the return value of the `fun()` call
383
+ * and returns the return value of the `fun()` call
378
384
 
379
385
  ## Recursion ##
380
386
  The call stack is also helpful to visualize how recursion works. Here we use `mg.block()` to show each step of how recursively `factorial(4)` is computed:
@@ -474,6 +480,7 @@ The ```mg.stack()``` doesn't work well in **watch** context in most debuggers be
474
480
  |:---|:---|
475
481
  | [pdb](https://docs.python.org/3/library/pdb.html), [pudb](https://pypi.org/project/pudb/) | `mg.stack_pdb()` |
476
482
  | [Visual Studio Code](https://code.visualstudio.com/docs/languages/python) | `mg.stack_vscode()` |
483
+ | [Jupyter Notebooks in VS Code](https://code.visualstudio.com/docs/datascience/jupyter-notebooks) | `mg.stack_vscode_jupyter()` |
477
484
  | [Cursor AI](https://www.cursor.com/) | `mg.stack_cursor()` |
478
485
  | [PyCharm](https://www.jetbrains.com/pycharm/) | `mg.stack_pycharm()` |
479
486
  | [Wing](https://wingware.com/) | `mg.stack_wing()` |
@@ -755,7 +762,7 @@ Different aspects of memory_graph can be configured. The default configuration c
755
762
  ## Simplified Graph ##
756
763
  Memory_graph simplifies the visualization (and the viewer's mental model) by **not** showing separate nodes for immutable types like `bool`, `int`, `float`, `complex`, and `str` by default. This simplification can sometimes be slightly misleading. As in the example below, after a shallow copy, lists `a` and `b` technically share their `int` values, but the graph makes it appear as though `a` and `b` each have their own copies. However, since `int` is immutable, this simplification will never lead to unexpected changes (changing `a`'s ints won’t affect `b`) so will never result in bugs.
757
764
 
758
- The simplification strikes a balance: it is slightly misleading but keeps the graph clean and easy to understand and focuses on the mutable types where unexpected changes can occur. This is why it is the default behavior. If you do want to show separate nodes for `int` values, such as for educational purposes, you can simply remove `int` from the `mg.config.embedded_types` set:
765
+ The simplification strikes a balance: it is slightly misleading but keeps the graph clean and easy to understand to focus on mutable types where unexpected changes can occur. This is why it is the default behavior. If you do want to show separate nodes for `int` values, such as for educational purposes, you can simply remove `int` from the `mg.config.embedded_types` set:
759
766
  ```python
760
767
  import memory_graph as mg
761
768
 
@@ -789,7 +796,7 @@ tree.insert(15, "fifteen")
789
796
 
790
797
  mg.show(locals())
791
798
  ```
792
- ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_fail.png)
799
+ ![avltree_fail.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_fail.png)
793
800
 
794
801
  ## All attributes using dir() ##
795
802
  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.
@@ -960,6 +967,8 @@ mg.config.type_to_node[List_View] = (lambda l: mg.Node_Linear(l,
960
967
  ```
961
968
  ![bin_search_linear.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_search_linear.png)
962
969
 
970
+ 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_search.py&breakpoints=32&continues=1&timestep=0.5&play)
971
+
963
972
  # Graph Depth #
964
973
  To limit the size of the graph the maximum depth of the graph is set by `mg.config.max_graph_depth`. Additionally for each type a depth can be set to further limit the graph, as is done for type `B` in the example below. Scissors indicate where the graph is cut short. Alternatively the `id()` of a data elements can be used to limit the graph for that specific element, as is done for the value referenced by variable `c`.
965
974
 
@@ -1032,12 +1041,12 @@ mg.show(locals())
1032
1041
  Different extensions are available for types from other Python packages.
1033
1042
 
1034
1043
  ## Numpy ##
1035
- Numpy types `array` and `matrix` and `ndarray` can be graphed with "memory_graph.extension_numpy":
1044
+ For Numpy types `array` and `matrix` and `ndarray`, use `mg.extend_numpy()`:
1036
1045
 
1037
1046
  ```python
1038
1047
  import memory_graph as mg
1039
1048
  import numpy as np
1040
- import memory_graph.extension_numpy
1049
+ mg.extend_numpy()
1041
1050
  np.random.seed(0) # use same random numbers each run
1042
1051
 
1043
1052
  matrix = np.matrix([[i*5+j for j in range(4)] for i in range(5)])
@@ -1052,12 +1061,12 @@ mg.show(locals())
1052
1061
  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).
1053
1062
 
1054
1063
  ## Pandas ##
1055
- Pandas types `Series` and `DataFrame` can be graphed with "memory_graph.extension_pandas":
1064
+ For pandas types `Series` and `DataFrame`, use `mg.extend_pandas()`:
1056
1065
 
1057
1066
  ```python
1058
1067
  import memory_graph as mg
1059
1068
  import pandas as pd
1060
- import memory_graph.extension_pandas
1069
+ mg.extend_pandas()
1061
1070
 
1062
1071
  series = pd.Series( [i for i in range(20)] )
1063
1072
  dataframe1 = pd.DataFrame({ "calories": [420, 380, 390],
@@ -1073,12 +1082,12 @@ mg.show(locals())
1073
1082
  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).
1074
1083
 
1075
1084
  ## PyTorch ##
1076
- Torch type `tensor` can be graphed with "memory_graph.extension_torch":
1085
+ For torch type `tensor`, use `mg.extend_torch()`:
1077
1086
 
1078
1087
  ```python
1079
1088
  import memory_graph as mg
1080
1089
  import torch
1081
- import memory_graph.extension_torch
1090
+ mg.extend_torch()
1082
1091
  torch.manual_seed(0) # same random numbers each run
1083
1092
 
1084
1093
  tensor_1d = torch.rand(3)
@@ -1151,4 +1160,4 @@ $ bash create_gif.sh animated
1151
1160
  - 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.
1152
1161
 
1153
1162
  # Other Packages #
1154
- 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.
1163
+ The [memory_graph](https://github.com/bterwijn/memory_graph) package visualizes your data. If instead you want to visualize function calls, check out the [invocation_tree](https://github.com/bterwijn/invocation_tree) package.
@@ -2,7 +2,7 @@
2
2
  # Copyright (c) 2023, Bas Terwijn.
3
3
  # SPDX-License-Identifier: BSD-2-Clause
4
4
 
5
- __version__ = "0.3.65"
5
+ __version__ = "0.3.67"
6
6
  __author__ = 'Bas Terwijn'
7
7
 
8
8
  import memory_graph.memory_to_nodes as memory_to_nodes
@@ -241,6 +241,51 @@ def stack_slice(begin_functions : List[Tuple[str, int]] = [],
241
241
  end_index = stack_end_index(stack_functions, begin_index, end_functions)
242
242
  return stack_frames_to_dict(reversed(frameInfos[begin_index+stack_index:end_index+1]))
243
243
 
244
+ def stack_multi_slice(drop_functions : List[Tuple[str, int]] = [],
245
+ end_functions : List[str] = ["<module>"],
246
+ stack_index : int = 0,
247
+ frameInfos : List[inspect.FrameInfo] = None,
248
+ ignore_frame_condition = None):
249
+ """
250
+ Returns a slice of the call stack, dropping all occurrences of the specified drop_functions.
251
+ Parameters:
252
+ drop_functions - list of (function-name, offset), drops all matching 'function-name' frames
253
+ including 'offset' number of parent frames for each match.
254
+ end_functions - list of function-names, ends at the index of the first 'function-name'
255
+ that is found in the call stack after stack index (inclusive),
256
+ otherwise ends at the last index
257
+ stack_index - number of frames removed from the beginning
258
+ ignore_frame_condition - optional callable that takes an inspect.FrameInfo and returns True if the frame should be dropped
259
+ """
260
+ if frameInfos is None:
261
+ frameInfos = inspect.stack()
262
+
263
+ stack_functions = [s.function for s in frameInfos]
264
+
265
+ # establish where to start (skipping innermost debugger eval frames)
266
+ begin_index = stack_begin_index(stack_functions, drop_functions)
267
+ if begin_index == 0:
268
+ begin_index += stack_index
269
+
270
+ end_index = stack_end_index(stack_functions, begin_index, end_functions)
271
+
272
+ drop_indices = set()
273
+ for drop_func, offset in drop_functions:
274
+ for i, func in enumerate(stack_functions[begin_index:end_index+1]):
275
+ actual_i = i + begin_index
276
+ if func == drop_func:
277
+ for j in range(actual_i, actual_i + offset):
278
+ drop_indices.add(j)
279
+
280
+ if ignore_frame_condition:
281
+ for i in range(begin_index, end_index + 1):
282
+ if i not in drop_indices and ignore_frame_condition(frameInfos[i]):
283
+ drop_indices.add(i)
284
+
285
+ filtered_frames = [frameInfos[i] for i in range(begin_index, end_index + 1) if i not in drop_indices]
286
+
287
+ return stack_frames_to_dict(reversed(filtered_frames))
288
+
244
289
  def stack(end_functions=["<module>"], stack_index=0):
245
290
  return stack_slice([], end_functions, stack_index+2)
246
291
 
@@ -275,6 +320,56 @@ def stack_wing(begin_functions=[("_py_line_event",1), ("_py_return_event",1)],
275
320
  return stack_slice(begin_functions, end_functions, stack_index)
276
321
 
277
322
 
323
+ banned_vscode_jupyter_strings = ['pydevd', 'debugpy', 'ipython', 'ipykernel', 'asyncio', 'tornado', 'traitlets', 'runpy']
324
+
325
+ def is_vscode_jupyter_banned(frameInfo):
326
+ module_name = frameInfo.frame.f_globals.get('__name__', '')
327
+ filename = frameInfo.filename
328
+ func_name = frameInfo.function
329
+
330
+ module_name_lower = module_name.lower()
331
+ func_name_lower = func_name.lower()
332
+ filename_lower = filename.lower()
333
+
334
+ # Exempt user notebook/cell code from being banned.
335
+ if module_name == '__main__' or filename_lower.startswith('<ipython-input'):
336
+ return False
337
+
338
+ for b in banned_vscode_jupyter_strings:
339
+ if b in module_name_lower or b in func_name_lower or b in filename_lower:
340
+ return True
341
+
342
+ return False
343
+
344
+ def stack_vscode_jupyter(drop_functions=[("trace_dispatch",1), ("_line_event",1), ("_return_event",1), ("do_wait_suspend",1), ("_do_wait_suspend",2)],
345
+ end_functions=["<module>"],
346
+ stack_index=0):
347
+ """ Get the call stack natively in a 'vscode' debugging 'jupyter' notebook session,
348
+ filtering out the noisy environment specific functions and modules that pollute the graph. """
349
+ # stack_index + 2 to account for stack_vscode_jupyter and stack_multi_slice
350
+ call_stack_dict = stack_multi_slice(drop_functions, end_functions, stack_index + 2, ignore_frame_condition=is_vscode_jupyter_banned)
351
+
352
+ for level_key, local_dict in call_stack_dict.items():
353
+ if '__pydevd_ret_val_dict' in local_dict:
354
+ ret_vals = local_dict.pop('__pydevd_ret_val_dict')
355
+ if isinstance(ret_vals, dict):
356
+ for k, v in ret_vals.items():
357
+ # filter out Jupyter internal return values, particularly I/O stream writes
358
+ if not any(b in k.lower() for b in banned_vscode_jupyter_strings) and 'write' not in k.lower():
359
+ # Place return value in the function's own frame if it is still on the stack
360
+ target_dict = local_dict
361
+ for l_key, l_dict in call_stack_dict.items():
362
+ if ": " in l_key and l_key.split(": ", 1)[1] == k:
363
+ target_dict = l_dict
364
+ break
365
+ target_dict[f"<return> {k}"] = v
366
+
367
+ if "<module>" in level_key:
368
+ call_stack_dict[level_key] = jupyter_locals_filter(local_dict)
369
+
370
+ return call_stack_dict
371
+
372
+
278
373
  def save_call_stack(filename):
279
374
  """ Saves the call stack to 'filename' for inspection to see what functions need to be
280
375
  filtered out to create the desired graph. """
@@ -310,6 +405,12 @@ def wing(filename=None, data=None):
310
405
  data = stack_wing()
311
406
  render(data, filename)
312
407
 
408
+ def vscode_jupyter(filename=None, data=None):
409
+ if data is None:
410
+ # stack_index=1 since this function counts as an extra stack frame
411
+ data = stack_vscode_jupyter(stack_index=1)
412
+ render(data, filename)
413
+
313
414
  def locals_filter(locals, keys):
314
415
  """ Filter out the jupyter specific keys that polute the graph. """
315
416
  return {k:v for k,v in utils.filter_dict(jupyter_locals)
@@ -391,3 +492,26 @@ def stack_marimo(end_functions=["<cell line: 0>", "<module>"], stack_index=0):
391
492
  globals_frame = next(iter(call_stack))
392
493
  call_stack[globals_frame] = marimo_locals_filter(call_stack[globals_frame])
393
494
  return call_stack
495
+
496
+ # ------------ extensions
497
+
498
+ def extend_numpy(on=True):
499
+ import memory_graph.extension_numpy as ext_np
500
+ if on:
501
+ ext_np.extend_numpy()
502
+ else:
503
+ ext_np.unextend_numpy()
504
+
505
+ def extend_pandas(on=True):
506
+ import memory_graph.extension_pandas as ext_pd
507
+ if on:
508
+ ext_pd.extend_pandas()
509
+ else:
510
+ ext_pd.unextend_pandas()
511
+
512
+ def extend_torch(on=True):
513
+ import memory_graph.extension_torch as ext_torch
514
+ if on:
515
+ ext_torch.extend_torch()
516
+ else:
517
+ ext_torch.unextend_torch()
@@ -7,7 +7,7 @@
7
7
  reopen_viewer = None
8
8
  render_filename = None
9
9
 
10
- type_lables = None
10
+ type_labels = None
11
11
 
12
12
  block_prints_location = None
13
13
  press_enter_message = None
@@ -0,0 +1,48 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ """ Extension to add the memory graph configuration for Numpy types. """
6
+ from memory_graph.node_linear import Node_Linear
7
+ from memory_graph.node_table import Node_Table
8
+
9
+ import memory_graph.config as config
10
+
11
+ def ndarray_to_node(data, ndarray_data):
12
+ dim = len(ndarray_data.shape)
13
+ if dim > 2:
14
+ return Node_Linear(data, [i for i in ndarray_data])
15
+ elif dim == 2:
16
+ return Node_Table(data, ndarray_data)
17
+ else:
18
+ return Node_Linear(data, ndarray_data)
19
+
20
+ def get_numpy_immutables():
21
+ import numpy as np
22
+
23
+ return {
24
+ np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64,
25
+ np.float16, np.float32, np.float64,
26
+ np.complex64, np.complex128,
27
+ np.bool_, np.bytes_, np.str_, np.datetime64, np.timedelta64
28
+ }
29
+
30
+ def extend_numpy():
31
+ import numpy as np
32
+
33
+ config.embedded_types |= get_numpy_immutables()
34
+ config.type_to_node[np.matrix] = lambda data : Node_Table(data, np.asarray(data)) # convert to ndarray to avoid infinite recursion due to index issue
35
+ config.type_to_node[np.ndarray] = lambda data : ndarray_to_node(data, data)
36
+
37
+ config.type_to_color[np.ndarray] = "hotpink1"
38
+ config.type_to_color[np.matrix] = "hotpink2"
39
+
40
+ def unextend_numpy():
41
+ import numpy as np
42
+
43
+ config.embedded_types -= get_numpy_immutables()
44
+ del config.type_to_node[np.matrix]
45
+ del config.type_to_node[np.ndarray]
46
+
47
+ del config.type_to_color[np.ndarray]
48
+ del config.type_to_color[np.matrix]
@@ -0,0 +1,36 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ """ Extension to add the memory graph configuration for Pandas types. """
6
+ from memory_graph.node_linear import Node_Linear
7
+ from memory_graph.node_table import Node_Table
8
+
9
+ import memory_graph.config as config
10
+
11
+ def extend_pandas():
12
+ import pandas as pd
13
+
14
+ config.type_to_node[pd.DataFrame] = lambda data : (
15
+ Node_Table(data,
16
+ data.values.tolist(),
17
+ col_names = data.columns.tolist(),
18
+ row_names = [ str(i) for i in data.index.tolist()]
19
+ )
20
+ )
21
+
22
+ config.type_to_node[pd.Series] = lambda data : (
23
+ Node_Linear(data, data.tolist())
24
+ )
25
+
26
+ config.type_to_color[pd.DataFrame] = "olivedrab1"
27
+ config.type_to_color[pd.Series] = "olivedrab2"
28
+
29
+ def unextend_pandas():
30
+ import pandas as pd
31
+
32
+ del config.type_to_node[pd.DataFrame]
33
+ del config.type_to_node[pd.Series]
34
+
35
+ del config.type_to_color[pd.DataFrame]
36
+ del config.type_to_color[pd.Series]
@@ -0,0 +1,19 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ """ Extension to add the memory graph configuration for Pandas types. """
6
+ import memory_graph.extension_numpy as ext_np
7
+ import memory_graph.config as config
8
+
9
+ def extend_torch():
10
+ import torch
11
+
12
+ config.type_to_node[torch.Tensor] = lambda data : ext_np.ndarray_to_node(data, data.numpy())
13
+ config.type_to_color[torch.Tensor] = "darkolivegreen1"
14
+
15
+ def unextend_torch():
16
+ import torch
17
+
18
+ del config.type_to_node[torch.Tensor]
19
+ del config.type_to_color[torch.Tensor]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memory_graph
3
- Version: 0.3.65
3
+ Version: 0.3.67
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-Expression: BSD-2-Clause
@@ -30,11 +30,11 @@ Additionally [Graphviz](https://graphviz.org/download/) needs to be installed.
30
30
  Run a live demo in the 👉 [**Memory Graph Web Debugger**](https://memory-graph.com/#breakpoints=8&continues=1&timestep=1.0&play) 👈 now, no installation required!
31
31
 
32
32
  - learn the right **mental model** to think about Python data (references, mutability, shallow vs deep copy)
33
- - **visualize the structure of your data** to easily understand and debug any data structure
33
+ - **visualize the structure of your data** to more easily understand and debug any data structure
34
34
  - understand function calls, variable scope, and the **complete program state** through call stack visualization
35
35
 
36
36
  An example Binary Tree data structure:
37
- ![images/bin_tree.gif](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_tree.gif)
37
+ ![images/bin_tree_vs.gif](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_tree_vs.gif)
38
38
  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&timestep=0.2&play).
39
39
 
40
40
  # Videos #
@@ -43,7 +43,7 @@ Or see it in the [Memory Grah Web Debugger](https://memory-graph.com/#codeurl=ht
43
43
  | [Quick Intro (3:49)](https://www.youtube.com/watch?v=23_bHcr7hqo) | [Mutability (17:29)](https://www.youtube.com/watch?v=pvIJgHCaXhU) |
44
44
 
45
45
  # Memory Graph #
46
- For program understanding and debugging, the [memory_graph](https://pypi.org/project/memory-graph/) package can visualize your data, supporting many different data types, including but not limited to:
46
+ For program understanding and debugging, the [memory_graph](https://github.com/bterwijn/memory_graph) package can visualize your data, supporting many different data types, including but not limited to:
47
47
 
48
48
  ```python
49
49
  import memory_graph as mg
@@ -69,7 +69,7 @@ mg.render(data, "my_graph.gv") # Graphviz DOT file
69
69
  mg.render(data) # renders to default: 'memory_graph.pdf'
70
70
  ```
71
71
 
72
- # Sharing Values #
72
+ # Sharing Values, Aliasing #
73
73
  In Python, assigning a list from variable `a` to variable `b` causes both variables to reference the same list value and thus share it. Consequently, any change applied through one variable will impact the other. This behavior can lead to elusive bugs if a programmer incorrectly assumes that list `a` and `b` are independent.
74
74
 
75
75
  <table><tr><td>
@@ -86,7 +86,7 @@ b.append(1) # changing 'b' changes 'a'
86
86
  print('a:', a)
87
87
  print('b:', b)
88
88
 
89
- # check if 'a' and 'b' share values
89
+ # check if 'a' and 'b' share the list
90
90
  print('ids:', id(a), id(b))
91
91
  print('identical?:', a is b)
92
92
 
@@ -98,18 +98,18 @@ mg.show( locals() )
98
98
 
99
99
  ![mutable2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/mutable2.png)
100
100
 
101
- a graph showing `a` and `b` share values
101
+ a graph showing `a` and `b` share the list
102
102
 
103
103
  </td></tr></table>
104
104
 
105
- The fact that `a` and `b` share values can not be verified by printing the lists. It can be verified by comparing the identity of both variables using the `id()` function or by using the `is` comparison operator as shown in the program output below, but this quickly becomes impractical for larger programs.
105
+ The fact that `a` and `b` share the list can not be verified by printing the lists. It can be verified by comparing the identity of both variables using the `id()` function or by using the `is` comparison operator as shown in the program output below, but this quickly becomes impractical for larger programs.
106
106
  ```{verbatim}
107
107
  a: 4, 3, 2, 1
108
108
  b: 4, 3, 2, 1
109
109
  ids: 126432214913216 126432214913216
110
110
  identical?: True
111
111
  ```
112
- A better way to understand what values are shared is to draw a graph using [memory_graph](https://pypi.org/project/memory-graph/).
112
+ A better way to understand what values are shared is to draw a graph using [memory_graph](https://github.com/bterwijn/memory_graph).
113
113
 
114
114
  # Topics #
115
115
 
@@ -161,7 +161,7 @@ Bas Terwijn
161
161
  ## Inspiration ##
162
162
  Inspired by [Python Tutor](https://pythontutor.com/).
163
163
 
164
- The main differences are that running memory_graph locally is a key design choice to support Python Tutor’s [unsupported features](https://github.com/pythontutor-dev/pythontutor/blob/master/unsupported-features.md#unsupported-features) and mirroring the data’s hierarchy improves graph readability.
164
+ The main differences are that by running memory_graph locally we support Python Tutor’s [unsupported features](https://github.com/pythontutor-dev/pythontutor/blob/master/unsupported-features.md#unsupported-features) so that it scales to full programs in many environments and IDEs instead of just code snippets in a webbrowser, and by mirroring the data’s hierarchy we improve graph readability for larger graphs.
165
165
 
166
166
  ## Social Media #
167
167
  * [LinkedIn](https://www.linkedin.com/groups/13244150/)
@@ -271,22 +271,28 @@ mg.show(locals())
271
271
  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).
272
272
 
273
273
  ## Name Rebinding ##
274
- 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 affecting any other variable.
274
+ 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 affecting any other variable.
275
+
276
+ Also note the difference between statement `b += [1]` that changes `b` and `a`, and statement `c = c + [300]` that first creates the new value `c + [300]` and assigns this value to `c` without effecting `b`. This shows that `x += y` is not the same as `x = x + y` for values of mutable type.
275
277
 
276
278
  ```python
277
279
  import memory_graph as mg
278
280
 
279
- a = [4, 3, 2]
281
+ a = [100, 200]
280
282
  b = a
281
283
  mg.render(locals(), 'rebinding1.png')
282
284
 
283
- b += [1] # changes the value of 'b' and 'a'
284
- b = [100, 200] # rebinds 'b' to another value, 'a' is unaffected
285
+ b += [300] # changes the value of 'b' and 'a'
286
+ b = [400, 500] # rebinds 'b' to a new value, 'a' is unaffected
287
+ c = b
285
288
  mg.render(locals(), 'rebinding2.png')
289
+
290
+ c = c + [600] # rebinds 'c' to new value 'c + [600]', `b` is unaffected
291
+ mg.render(locals(), 'rebinding3.png')
286
292
  ```
287
- | ![rebinding1.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding1.png) | ![rebinding2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding2.png) |
288
- |:-----------------------------------------------------------:|:-------------------------------------------------------------:|
289
- | rebinding1.png | rebinding2.png |
293
+ | ![rebinding1.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding1.png) | ![rebinding2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding2.png) | ![rebinding3.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/rebinding3.png) |
294
+ |:--------------:|:--------------:|:--------------:|
295
+ | rebinding1.png | rebinding2.png | rebinding3.png |
290
296
 
291
297
  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/rebind.py&play).
292
298
 
@@ -314,7 +320,7 @@ When copying a mix of values of mutable and immutable type, to save on time and
314
320
  import memory_graph as mg
315
321
  import copy
316
322
 
317
- a = ( [1, 2], ('x', 'y') ) # mix of mutable and immutable
323
+ a = ( [1, 2], ('x', 'y') ) # mix of mutable and immutable values
318
324
 
319
325
  # three different ways to make a "copy" of 'a':
320
326
  c1 = a
@@ -381,7 +387,7 @@ The effect of calling `add_one()` is that `b[0]` increases by 1, while `a` is un
381
387
 
382
388
  # Data Model Exercises #
383
389
 
384
- Now is a good time to practice with Python Data Model concepts. Here are [some exercises](https://github.com/bterwijn/memory_graph_videos/blob/main/exercises/exercises.md) on references, mutability, copies, and function calls.
390
+ Now is a good time to practice with these Python Data Model concepts. Here are [some exercises](https://github.com/bterwijn/memory_graph_videos/blob/main/exercises/exercises.md) on references, mutability, copies, and function calls. Also see the programming exercises at [the end of the Mutability video](https://www.youtube.com/watch?v=pvIJgHCaXhU&t=891s).
385
391
 
386
392
  # Block #
387
393
  It is often helpful to temporarily block program execution to inspect the graph. For this we can use the `mg.block()` function:
@@ -394,7 +400,7 @@ This function:
394
400
  * first executes `fun(arg1, arg2, ...)`
395
401
  * then prints the current source location in the program
396
402
  * then blocks execution until the &lt;Enter&gt; key is pressed
397
- * finally returns the return value of the `fun()` call
403
+ * and returns the return value of the `fun()` call
398
404
 
399
405
  ## Recursion ##
400
406
  The call stack is also helpful to visualize how recursion works. Here we use `mg.block()` to show each step of how recursively `factorial(4)` is computed:
@@ -494,6 +500,7 @@ The ```mg.stack()``` doesn't work well in **watch** context in most debuggers be
494
500
  |:---|:---|
495
501
  | [pdb](https://docs.python.org/3/library/pdb.html), [pudb](https://pypi.org/project/pudb/) | `mg.stack_pdb()` |
496
502
  | [Visual Studio Code](https://code.visualstudio.com/docs/languages/python) | `mg.stack_vscode()` |
503
+ | [Jupyter Notebooks in VS Code](https://code.visualstudio.com/docs/datascience/jupyter-notebooks) | `mg.stack_vscode_jupyter()` |
497
504
  | [Cursor AI](https://www.cursor.com/) | `mg.stack_cursor()` |
498
505
  | [PyCharm](https://www.jetbrains.com/pycharm/) | `mg.stack_pycharm()` |
499
506
  | [Wing](https://wingware.com/) | `mg.stack_wing()` |
@@ -775,7 +782,7 @@ Different aspects of memory_graph can be configured. The default configuration c
775
782
  ## Simplified Graph ##
776
783
  Memory_graph simplifies the visualization (and the viewer's mental model) by **not** showing separate nodes for immutable types like `bool`, `int`, `float`, `complex`, and `str` by default. This simplification can sometimes be slightly misleading. As in the example below, after a shallow copy, lists `a` and `b` technically share their `int` values, but the graph makes it appear as though `a` and `b` each have their own copies. However, since `int` is immutable, this simplification will never lead to unexpected changes (changing `a`'s ints won’t affect `b`) so will never result in bugs.
777
784
 
778
- The simplification strikes a balance: it is slightly misleading but keeps the graph clean and easy to understand and focuses on the mutable types where unexpected changes can occur. This is why it is the default behavior. If you do want to show separate nodes for `int` values, such as for educational purposes, you can simply remove `int` from the `mg.config.embedded_types` set:
785
+ The simplification strikes a balance: it is slightly misleading but keeps the graph clean and easy to understand to focus on mutable types where unexpected changes can occur. This is why it is the default behavior. If you do want to show separate nodes for `int` values, such as for educational purposes, you can simply remove `int` from the `mg.config.embedded_types` set:
779
786
  ```python
780
787
  import memory_graph as mg
781
788
 
@@ -809,7 +816,7 @@ tree.insert(15, "fifteen")
809
816
 
810
817
  mg.show(locals())
811
818
  ```
812
- ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_fail.png)
819
+ ![avltree_fail.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_fail.png)
813
820
 
814
821
  ## All attributes using dir() ##
815
822
  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.
@@ -980,6 +987,8 @@ mg.config.type_to_node[List_View] = (lambda l: mg.Node_Linear(l,
980
987
  ```
981
988
  ![bin_search_linear.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_search_linear.png)
982
989
 
990
+ 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_search.py&breakpoints=32&continues=1&timestep=0.5&play)
991
+
983
992
  # Graph Depth #
984
993
  To limit the size of the graph the maximum depth of the graph is set by `mg.config.max_graph_depth`. Additionally for each type a depth can be set to further limit the graph, as is done for type `B` in the example below. Scissors indicate where the graph is cut short. Alternatively the `id()` of a data elements can be used to limit the graph for that specific element, as is done for the value referenced by variable `c`.
985
994
 
@@ -1052,12 +1061,12 @@ mg.show(locals())
1052
1061
  Different extensions are available for types from other Python packages.
1053
1062
 
1054
1063
  ## Numpy ##
1055
- Numpy types `array` and `matrix` and `ndarray` can be graphed with "memory_graph.extension_numpy":
1064
+ For Numpy types `array` and `matrix` and `ndarray`, use `mg.extend_numpy()`:
1056
1065
 
1057
1066
  ```python
1058
1067
  import memory_graph as mg
1059
1068
  import numpy as np
1060
- import memory_graph.extension_numpy
1069
+ mg.extend_numpy()
1061
1070
  np.random.seed(0) # use same random numbers each run
1062
1071
 
1063
1072
  matrix = np.matrix([[i*5+j for j in range(4)] for i in range(5)])
@@ -1072,12 +1081,12 @@ mg.show(locals())
1072
1081
  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).
1073
1082
 
1074
1083
  ## Pandas ##
1075
- Pandas types `Series` and `DataFrame` can be graphed with "memory_graph.extension_pandas":
1084
+ For pandas types `Series` and `DataFrame`, use `mg.extend_pandas()`:
1076
1085
 
1077
1086
  ```python
1078
1087
  import memory_graph as mg
1079
1088
  import pandas as pd
1080
- import memory_graph.extension_pandas
1089
+ mg.extend_pandas()
1081
1090
 
1082
1091
  series = pd.Series( [i for i in range(20)] )
1083
1092
  dataframe1 = pd.DataFrame({ "calories": [420, 380, 390],
@@ -1093,12 +1102,12 @@ mg.show(locals())
1093
1102
  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).
1094
1103
 
1095
1104
  ## PyTorch ##
1096
- Torch type `tensor` can be graphed with "memory_graph.extension_torch":
1105
+ For torch type `tensor`, use `mg.extend_torch()`:
1097
1106
 
1098
1107
  ```python
1099
1108
  import memory_graph as mg
1100
1109
  import torch
1101
- import memory_graph.extension_torch
1110
+ mg.extend_torch()
1102
1111
  torch.manual_seed(0) # same random numbers each run
1103
1112
 
1104
1113
  tensor_1d = torch.rand(3)
@@ -1171,4 +1180,4 @@ $ bash create_gif.sh animated
1171
1180
  - 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.
1172
1181
 
1173
1182
  # Other Packages #
1174
- 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.
1183
+ The [memory_graph](https://github.com/bterwijn/memory_graph) package visualizes your data. If instead you want to visualize function calls, check out the [invocation_tree](https://github.com/bterwijn/invocation_tree) package.
@@ -1,7 +1,6 @@
1
1
  LICENSE.txt
2
2
  README.md
3
3
  pyproject.toml
4
- setup.py
5
4
  memory_graph/__init__.py
6
5
  memory_graph/call_stack.py
7
6
  memory_graph/config.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memory_graph"
7
- version = "0.3.65"
7
+ version = "0.3.67"
8
8
  description = "Teaching tool and debugging aid in context of references, mutable data types, and shallow and deep copy."
9
9
  authors = [
10
10
  {name = "Bas Terwijn", email = "bterwijn@gmail.com"}
@@ -1,33 +0,0 @@
1
- # This file is part of memory_graph.
2
- # Copyright (c) 2023, Bas Terwijn.
3
- # SPDX-License-Identifier: BSD-2-Clause
4
-
5
- """ Extension to add the memory graph configuration for Numpy types. """
6
- from memory_graph.node_linear import Node_Linear
7
- from memory_graph.node_table import Node_Table
8
-
9
- import memory_graph.config as config
10
-
11
- import numpy as np
12
-
13
- config.embedded_types |= {
14
- np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64,
15
- np.float16, np.float32, np.float64,
16
- np.complex64, np.complex128,
17
- np.bool_, np.bytes_, np.str_, np.datetime64, np.timedelta64
18
- }
19
-
20
- def ndarray_to_node(data, ndarray_data):
21
- dim = len(ndarray_data.shape)
22
- if dim > 2:
23
- return Node_Linear(data, [i for i in ndarray_data])
24
- elif dim == 2:
25
- return Node_Table(data, ndarray_data)
26
- else:
27
- return Node_Linear(data, ndarray_data)
28
-
29
- config.type_to_node[np.matrix] = lambda data : Node_Table(data, np.asarray(data)) # convert to ndarray to avoid infinite recursion due to index issue
30
- config.type_to_node[np.ndarray] = lambda data : ndarray_to_node(data, data)
31
-
32
- config.type_to_color[np.ndarray] = "hotpink1"
33
- config.type_to_color[np.matrix] = "hotpink2"
@@ -1,26 +0,0 @@
1
- # This file is part of memory_graph.
2
- # Copyright (c) 2023, Bas Terwijn.
3
- # SPDX-License-Identifier: BSD-2-Clause
4
-
5
- """ Extension to add the memory graph configuration for Pandas type. """
6
- from memory_graph.node_linear import Node_Linear
7
- from memory_graph.node_table import Node_Table
8
-
9
- import memory_graph.config as config
10
-
11
- import pandas as pd
12
-
13
- config.type_to_node[pd.DataFrame] = lambda data : (
14
- Node_Table(data,
15
- data.values.tolist(),
16
- col_names = data.columns.tolist(),
17
- row_names = [ str(i) for i in data.index.tolist()]
18
- )
19
- )
20
-
21
- config.type_to_node[pd.Series] = lambda data : (
22
- Node_Linear(data, data.tolist())
23
- )
24
-
25
- config.type_to_color[pd.DataFrame] = "olivedrab1"
26
- config.type_to_color[pd.Series] = "olivedrab2"
@@ -1,6 +0,0 @@
1
- import memory_graph.extension_numpy as ext_np
2
- import memory_graph.config as config
3
- import torch
4
-
5
- config.type_to_node[torch.Tensor] = lambda data : ext_np.ndarray_to_node(data, data.numpy())
6
- config.type_to_color[torch.Tensor] = "darkolivegreen1"
@@ -1,7 +0,0 @@
1
- from setuptools import setup
2
-
3
- setup(
4
- name="memory-graph",
5
- version="0.3.30",
6
- packages=["memory_graph"],
7
- )
File without changes
File without changes