memory-graph 0.1.27__tar.gz → 0.2.1__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 (42) hide show
  1. memory_graph-0.2.1/PKG-INFO +560 -0
  2. memory_graph-0.2.1/README.md +539 -0
  3. memory_graph-0.2.1/memory_graph/HTML_Table.py +133 -0
  4. memory_graph-0.2.1/memory_graph/Memory_Graph.py +76 -0
  5. memory_graph-0.2.1/memory_graph/Memory_Visitor.py +81 -0
  6. memory_graph-0.2.1/memory_graph/Node.py +111 -0
  7. memory_graph-0.2.1/memory_graph/Node_Hidden.py +26 -0
  8. memory_graph-0.2.1/memory_graph/Node_Key_Value.py +93 -0
  9. memory_graph-0.2.1/memory_graph/Node_Linear.py +66 -0
  10. memory_graph-0.2.1/memory_graph/Node_Table.py +84 -0
  11. memory_graph-0.2.1/memory_graph/Sliced.py +111 -0
  12. memory_graph-0.2.1/memory_graph/Slicer.py +169 -0
  13. memory_graph-0.2.1/memory_graph/__init__.py +148 -0
  14. memory_graph-0.2.1/memory_graph/config.py +15 -0
  15. memory_graph-0.2.1/memory_graph/config_default.py +83 -0
  16. memory_graph-0.2.1/memory_graph/config_helpers.py +70 -0
  17. memory_graph-0.2.1/memory_graph/extension_numpy.py +44 -0
  18. memory_graph-0.2.1/memory_graph/extension_pandas.py +22 -0
  19. memory_graph-0.2.1/memory_graph/special_types.py +4 -0
  20. memory_graph-0.2.1/memory_graph/test.py +178 -0
  21. memory_graph-0.2.1/memory_graph/test_memory_graph.py +13 -0
  22. memory_graph-0.2.1/memory_graph/test_memory_visitor.py +10 -0
  23. memory_graph-0.2.1/memory_graph/utils.py +73 -0
  24. memory_graph-0.2.1/memory_graph.egg-info/PKG-INFO +560 -0
  25. memory_graph-0.2.1/memory_graph.egg-info/SOURCES.txt +30 -0
  26. {memory_graph-0.1.27 → memory_graph-0.2.1}/setup.py +2 -2
  27. memory_graph-0.1.27/PKG-INFO +0 -525
  28. memory_graph-0.1.27/README.md +0 -504
  29. memory_graph-0.1.27/memory_graph/Node.py +0 -85
  30. memory_graph-0.1.27/memory_graph/__init__.py +0 -124
  31. memory_graph-0.1.27/memory_graph/graphviz_nodes.py +0 -199
  32. memory_graph-0.1.27/memory_graph/rewrite.py +0 -148
  33. memory_graph-0.1.27/memory_graph/rewrite_to_node.py +0 -37
  34. memory_graph-0.1.27/memory_graph/rewrite_to_string.py +0 -26
  35. memory_graph-0.1.27/memory_graph.egg-info/PKG-INFO +0 -525
  36. memory_graph-0.1.27/memory_graph.egg-info/SOURCES.txt +0 -15
  37. {memory_graph-0.1.27 → memory_graph-0.2.1}/LICENSE.txt +0 -0
  38. {memory_graph-0.1.27 → memory_graph-0.2.1}/MANIFEST.in +0 -0
  39. {memory_graph-0.1.27 → memory_graph-0.2.1}/memory_graph.egg-info/dependency_links.txt +0 -0
  40. {memory_graph-0.1.27 → memory_graph-0.2.1}/memory_graph.egg-info/requires.txt +0 -0
  41. {memory_graph-0.1.27 → memory_graph-0.2.1}/memory_graph.egg-info/top_level.txt +0 -0
  42. {memory_graph-0.1.27 → memory_graph-0.2.1}/setup.cfg +0 -0
@@ -0,0 +1,560 @@
1
+ Metadata-Version: 2.1
2
+ Name: memory_graph
3
+ Version: 0.2.1
4
+ Summary: Draws a graph of your data to analyze its structure.
5
+ Home-page: https://github.com/bterwijn/memory_graph
6
+ Author: Bas Terwijn
7
+ Author-email: bterwijn@gmail.com
8
+ License: BSD 2-clause
9
+ Platform: UNKNOWN
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Education
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Education
16
+ Classifier: Topic :: Software Development :: Debuggers
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE.txt
19
+
20
+ # Installation #
21
+ Install (or upgrade) `memory_graph` using pip:
22
+ ```
23
+ pip install --upgrade memory_graph
24
+ ```
25
+ Additionally [Graphviz](https://graphviz.org/download/) needs to be installed.
26
+
27
+ # Sharing Data #
28
+
29
+ In Python, assigning the list from variable `a` to variable `b` causes both variables to reference the same list object and therefore share the data. 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.
30
+
31
+ <table><tr><td>
32
+
33
+ ```python
34
+ import memory_graph
35
+
36
+ # create the lists 'a' and 'b'
37
+ a = [4, 3, 2]
38
+ b = a
39
+ a.append(1) # changing 'a' changes 'b'
40
+
41
+ # print the 'a' and 'b' list
42
+ print('a:', a)
43
+ print('b:', b)
44
+
45
+ # check if 'a' and 'b' share data
46
+ print('ids:', id(a), id(b))
47
+ print('identical?:', a is b)
48
+
49
+ # show all local variables in a graph
50
+ memory_graph.show( locals() )
51
+ ```
52
+
53
+ </td><td>
54
+
55
+ ![mutable2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/mutable2.png)
56
+
57
+ a graph showing `a` and `b` share data
58
+
59
+ </td></tr></table>
60
+
61
+ The fact that `a` and `b` share data 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.
62
+ ```{verbatim}
63
+ a: 4, 3, 2, 1
64
+ b: 4, 3, 2, 1
65
+ ids: 126432214913216 126432214913216
66
+ identical?: True
67
+ ```
68
+ A better way to understand what data is shared is to draw a graph of the data using the [memory_graph](https://pypi.org/project/memory-graph/) package.
69
+
70
+ # Memory Graph Packge #
71
+ The [memory_graph](https://pypi.org/project/memory-graph/) package can graph many different data types.
72
+
73
+ ```python
74
+ import memory_graph
75
+
76
+ class MyClass:
77
+
78
+ def __init__(self, x, y):
79
+ self.x = x
80
+ self.y = y
81
+
82
+ data = [ range(1, 2), (3, 4), {5, 6}, {7:'seven', 8:'eight'}, MyClass(9, 10) ]
83
+ memory_graph.show(data, block=True)
84
+ ```
85
+ ![many_types.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/many_types.png)
86
+
87
+ By using `block=True` the program blocks until the ENTER key is pressed so you can view the graph before continuing program execution (and possibly viewing later graphs). Instead of showing the graph you can also render it to an output file of your choosing (see [Graphviz Output Formats](https://graphviz.org/docs/outputs/)) using for example:
88
+
89
+ ```python
90
+ memory_graph.render(data, "my_graph.pdf")
91
+ memory_graph.render(data, "my_graph.png")
92
+ memory_graph.render(data, "my_graph.gv") # Graphviz DOT file
93
+ ```
94
+
95
+ # Chapters #
96
+
97
+ [1. Python Data Model](#1-python-data-model)
98
+
99
+ [2. Debugging](#2-debugging)
100
+
101
+ [3. Call Stack](#3-call-stack)
102
+
103
+ [4. Datastructure Examples](#4-datastructure-examples)
104
+
105
+ [5. Configuration](#5-configuration)
106
+
107
+ [6. Extensions](#6-extensions)
108
+
109
+ [7. Jupyter Notebook](#7-jupyter-notebook)
110
+
111
+ [8. Troubleshooting](#8-troubleshooting)
112
+
113
+
114
+ ## Author ##
115
+ Bas Terwijn
116
+
117
+ ## Inspiration ##
118
+ Inspired by [Python Tutor](https://pythontutor.com/).
119
+
120
+ ___
121
+ ___
122
+
123
+ ## 1. Python Data Model ##
124
+ The [Python Data Model](https://docs.python.org/3/reference/datamodel.html) makes a distiction between immutable and mutable types:
125
+
126
+ * **immutable**: bool, int, float, complex, str, tuple, bytes, frozenset
127
+ * **mutable**: list, dict, set, class, ... (all other types)
128
+
129
+
130
+ ### Immutable Type ###
131
+ In the code below variable `a` and `b` both reference the same `int` value 10. An `int` is an immutable type and therefore when we change variable `a` its value can **not** be mutated in place, and thus a copy is made and `a` and `b` reference a different value afterwards.
132
+ ```python
133
+ import memory_graph
134
+ memory_graph.rewrite_to_node.reduce_reference_children.remove("int") # shows references to 'int'
135
+
136
+ a = 10
137
+ b = a
138
+ memory_graph.render(locals(), 'immutable1.png')
139
+ a += 1
140
+ memory_graph.render(locals(), 'immutable2.png')
141
+ ```
142
+ | ![mutable1.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/immutable1.png) | ![mutable2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/immutable2.png) |
143
+ |:-----------------------------------------------------------:|:-------------------------------------------------------------:|
144
+ | immutable1.png | immutable2.png |
145
+
146
+
147
+ ### Mutable Type ###
148
+ With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `b` is independent from `a`.
149
+
150
+ ```python
151
+ import memory_graph
152
+
153
+ a = [4, 3, 2]
154
+ b = a
155
+ memory_graph.render(locals(), 'mutable1.png')
156
+ a.append(1)
157
+ memory_graph.render(locals(), 'mutable2.png')
158
+ ```
159
+ | ![mutable1.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/mutable1.png) | ![mutable2.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/mutable2.png) |
160
+ |:-----------------------------------------------------------:|:-------------------------------------------------------------:|
161
+ | mutable1.png | mutable2.png |
162
+
163
+
164
+ Python makes this distiction between mutable and immutable types because a value of a mutable type generally could be large and therefore it would be slow to make a copy each time we change it. On the other hand, a value of a changable immutable type generally is small and therefore fast to copy.
165
+
166
+
167
+ ### Copying ###
168
+ Python offers three different "copy" options that we will demonstrate using a nested list:
169
+
170
+ ```python
171
+ import memory_graph
172
+ import copy
173
+
174
+ a = [ [1, 2], ['x', 'y'] ] # a nested list (a list containing lists)
175
+
176
+ # three different ways to make a "copy" of 'a':
177
+ c1 = a
178
+ c2 = copy.copy(a) # equivalent to: a.copy() a[:] list(a)
179
+ c3 = copy.deepcopy(a)
180
+
181
+ memory_graph.show(locals())
182
+ ```
183
+
184
+ * `c1` is an **assignment**, all the data is shared, nothing is copied
185
+ * `c2` is a **shallow copy**, only the data referenced by the first reference is copied and the underlying data is shared
186
+ * `c3` is a **deep copy**, all the data is copied, nothing is shared
187
+
188
+ ![copies.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/copies.png)
189
+
190
+
191
+ ### Custom Copy Method ###
192
+ We can write our own custom copy function or method in case the three "copy" options don't do what we want. For example the copy() method of My_Class in the code below copies the `digits` but shares the `letters` between the two objects.
193
+
194
+ ```python
195
+ import memory_graph
196
+ import copy
197
+
198
+ class My_Class:
199
+
200
+ def __init__(self):
201
+ self.digits = [1, 2]
202
+ self.letters = ['x', 'y']
203
+
204
+ def copy(self): # custom copy method copies the digits but shares the letters
205
+ c = copy.copy(self)
206
+ c.digits = copy.copy(self.digits)
207
+ return c
208
+
209
+ a = My_Class()
210
+ b = a.copy()
211
+
212
+ memory_graph.show(locals())
213
+ ```
214
+ ![copy_method.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/copy_method.png)
215
+
216
+
217
+ ## 2. Debugging ##
218
+ Often it is useful to graph all the local variables using:
219
+ ```python
220
+ memory_graph.show(locals(), block=True)
221
+ ```
222
+
223
+ So much so that function `d()` is available as alias for this for easier debugging. Additionally it can optionally log the data by printing them. For example:
224
+ ```python
225
+ from memory_graph import d
226
+
227
+ my_squares = []
228
+ my_squares_ref = my_squares
229
+ for i in range(5):
230
+ my_squares.append(i**2)
231
+ d(log=True)
232
+ my_squares_copy = my_squares.copy()
233
+ d(log=True)
234
+ ```
235
+ which after pressing ENTER a number of times results in:
236
+
237
+ ![debugging.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/debugging.png)
238
+ ```
239
+ my_squares: [0, 1, 4, 9, 16]
240
+ my_squares_ref: [0, 1, 4, 9, 16]
241
+ i: 4
242
+ my_squares_copy: [0, 1, 4, 9, 16]
243
+ ```
244
+
245
+ Function `d()` has these default arguments:
246
+ ```python
247
+ def d(data=None, graph=True, log=False, block=True):
248
+ ```
249
+ - data: the data that is handled, defaults to `locals()` when not specified
250
+ - graph: if True the data is visualized as a graph
251
+ - log: if True the data is printed
252
+ - block: if True the function blocks until the ENTER key is pressed
253
+
254
+ To print to a log file instead of standard output use:
255
+ ```python
256
+ memory_graph.log_file = open("my_log_file.txt", "w")
257
+ ```
258
+
259
+ ### Watchpoint in Debugger ###
260
+ Alternatively you get an even better debugging experience when you set expression:
261
+ ```
262
+ memory_graph.render(locals(), "my_debug_graph.pdf")
263
+ ```
264
+ as a *watchpoint* in a debugger tool and open the "my_debug_graph.pdf" output file. This continuouly shows the graph of all the local variables while debugging and avoids having to add any memory_graph `show()`, `render()`, or `d()` calls to your code.
265
+
266
+
267
+ ## 3. Call Stack ##
268
+ Function ```memory_graph.get_call_stack()``` returns the full call stack that holds for each called function all the local variables. This enables us to visualize the local variables of each of the called functions simultaneously. This helps to visualize if variables of different called functions share any data between them. Here for example we call function ```add_one()``` with arguments ```a, b, c``` that adds one to change each of its arguments.
269
+
270
+ ```python
271
+ import memory_graph
272
+
273
+ def add_one(a, b, c):
274
+ a += 1
275
+ b.append(1)
276
+ c.append(1)
277
+ memory_graph.show(memory_graph.get_call_stack())
278
+
279
+ a = 10
280
+ b = [4, 3, 2]
281
+ c = [4, 3, 2]
282
+
283
+ add_one(a, b, c.copy())
284
+ print(f"a:{a} b:{b} c:{c}")
285
+ ```
286
+ ![add_one.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/add_one.png)
287
+
288
+ As `a` is of immutable type 'int' and as we call the function with a copy of `c`, only `b` is shared so only `b` is changed in the calling stack frame as reflected in the printed output:
289
+ ```
290
+ a:0 b:[4, 3, 2, 1] c:[4, 3, 2]
291
+ ```
292
+
293
+ ### Recursion ###
294
+ The call stack also helps to visualize how recursion works. Here we show each step of how recursively ```factorial(3)``` is computed:
295
+
296
+ ```python
297
+ import memory_graph
298
+
299
+ def factorial(n):
300
+ if n==0:
301
+ return 1
302
+ memory_graph.show( memory_graph.get_call_stack(), block=True )
303
+ result = n * factorial(n-1)
304
+ memory_graph.show( memory_graph.get_call_stack(), block=True )
305
+ return result
306
+
307
+ factorial(3)
308
+ ```
309
+ <div><img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/factorial1.png" /></div>
310
+ <div><img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/factorial2.png" /></div>
311
+ <div><img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/factorial3.png" /></div>
312
+ <div><img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/factorial4.png" /></div>
313
+ <div><img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/factorial5.png" /></div>
314
+ <div><img src="https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/factorial6.png" /></div>
315
+ and the final result is: 1 x 2 x 3 = 6
316
+
317
+ ### Call Stack in Watchpoint ###
318
+ The ```memory_graph.get_call_stack()``` doesn't work well in a watchpoint context in most debuggers because debuggers introduce additional stack frames that cause problems. Use these alternative functions for various debuggers to filter out these problematic stack frames:
319
+
320
+ | debugger | function to get the call stack |
321
+ |:---|:---|
322
+ | **pdb, pudb** | `memory_graph.get_call_stack_pdb()` |
323
+ | **Visual Studio Code** | `memory_graph.get_call_stack_vscode()` |
324
+ | **Pycharm** | `memory_graph.get_call_stack_pycharm()` |
325
+
326
+ #### Other Debuggers ####
327
+ For other debuggers, invoke this function within the watchpoint context. Then, in the "call_stack.txt" file, identify the slice of functions you wish to include in the call stack, more specifically choise 'after' and 'up_to' what function you want to slice.
328
+ ```
329
+ memory_graph.save_call_stack("call_stack.txt")
330
+ ```
331
+ and then call this function to get the desired call stack to show in the graph:
332
+ ```
333
+ memory_graph.get_call_stack_after_up_to(after_function, up_to_function="<module>")
334
+ ```
335
+
336
+
337
+ ## 4. Datastructure Examples ##
338
+ Module memory_graph can be very useful in a course about datastructures, some examples:
339
+
340
+ ### Doubly Linked List ###
341
+ ```python
342
+ from memory_graph import d
343
+ import random
344
+ random.seed(0) # use same random numbers each run
345
+
346
+ class Node:
347
+
348
+ def __init__(self, value):
349
+ self.prev = None
350
+ self.value = value
351
+ self.next = None
352
+
353
+ class LinkedList:
354
+
355
+ def __init__(self):
356
+ self.head = None
357
+ self.tail = None
358
+
359
+ def add_front(self, value):
360
+ new_node = Node(value)
361
+ if self.head is None:
362
+ self.head = new_node
363
+ self.tail = new_node
364
+ else:
365
+ new_node.next = self.head
366
+ self.head.prev = new_node
367
+ self.head = new_node
368
+
369
+ linked_list = LinkedList()
370
+ n = 100
371
+ for i in range(n):
372
+ new_value = random.randrange(n)
373
+ linked_list.add_front(new_value)
374
+ d() # <--- draw linked list
375
+ ```
376
+ ![linked_list.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/linked_list.png)
377
+
378
+ ### Binary Tree ###
379
+ ```python
380
+ from memory_graph import d
381
+ import random
382
+ random.seed(0) # use same random numbers each run
383
+
384
+ class Node:
385
+
386
+ def __init__(self, value):
387
+ self.smaller = None
388
+ self.value = value
389
+ self.larger = None
390
+
391
+ class BinTree:
392
+
393
+ def __init__(self):
394
+ self.root = None
395
+
396
+ def add_recursive(self, new_value, node):
397
+ d() # <--- draw tree when adding recursively
398
+ if new_value < node.value:
399
+ if node.smaller is None:
400
+ node.smaller = Node(new_value)
401
+ else:
402
+ self.add_recursive(new_value, node.smaller)
403
+ else:
404
+ if node.larger is None:
405
+ node.larger = Node(new_value)
406
+ else:
407
+ self.add_recursive(new_value, node.larger)
408
+
409
+ def add(self, value):
410
+ if self.root is None:
411
+ self.root = Node(value)
412
+ else:
413
+ self.add_recursive(value, self.root)
414
+
415
+ tree = BinTree()
416
+ n = 100
417
+ for i in range(n):
418
+ new_value = random.randrange(100)
419
+ tree.add(new_value)
420
+ d() # <--- draw tree after adding
421
+ ```
422
+ ![bin_tree.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/bin_tree.png)
423
+
424
+ ### Hash Set ###
425
+ ```python
426
+ from memory_graph import d
427
+ import random
428
+ random.seed(0) # use same random numbers each run
429
+
430
+ class HashSet:
431
+
432
+ def __init__(self, capacity=20):
433
+ self.buckets = [None] * capacity
434
+
435
+ def add(self, value):
436
+ index = hash(value) % len(self.buckets)
437
+ if self.buckets[index] is None:
438
+ self.buckets[index] = [value]
439
+ else:
440
+ self.buckets[index].append(value)
441
+
442
+ def contains(self, value):
443
+ index = hash(value) % len(self.buckets)
444
+ if self.buckets[index] is None:
445
+ return False
446
+ return value in self.buckets[index]
447
+
448
+ def remove(self, value):
449
+ index = hash(value) % len(self.buckets)
450
+ if self.buckets[index] is not None:
451
+ self.buckets[index].remove(value)
452
+
453
+ hash_set = HashSet()
454
+ n = 100
455
+ for i in range(n):
456
+ new_value = random.randrange(n)
457
+ hash_set.add(new_value)
458
+ d() # <--- draw hash set
459
+ ```
460
+ ![hash_set.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/hash_set.png)
461
+
462
+
463
+ ## 5. Configuration ##
464
+ Different aspects of memory_graph can be configured. The default configuration is reset by importing 'memory_graph.config_default'.
465
+
466
+ - ***memory_graph.config.max_number_nodes*** : int
467
+ - The maxium number of Nodes shown in the graph. When the graph gets too big set this to a smaller number. A `★` symbol indictes where the graph is cut short.
468
+
469
+ - ***memory_graph.config.max_string_length*** : int
470
+ - The maximum length of strings shown in the graph. Longer strings will be truncated.
471
+
472
+ - ***memory_graph.config.no_reference_types*** : dict
473
+ - Holds all types for which no seperate node is drawn but that instead are shown as elements in their parent Node. It maps each type to a function that determines how it is visualized.
474
+
475
+ - ***memory_graph.config.no_child_references_types*** : set
476
+ - The set of key_value types that don't draw references to their direct childeren but have their children shown as elements of their node.
477
+
478
+ - ***memory_graph.config.type_to_node*** : dict
479
+ - Determines how a data types is converted to a Node (sub)class for visualization in the graph.
480
+
481
+ - ***memory_graph.config.type_to_color*** : dict
482
+ - Maps each type to the [graphviz color](https://graphviz.org/doc/info/colors.html) it gets in the graph.
483
+
484
+ - ***memory_graph.config.type_to_vertical_orientation*** : dict
485
+ - Maps each type to its orientation. Use 'True' for vertical and 'False' for horizontal. If not specified Node_Linear and Node_Key_Value are vertical unless they have references to children.
486
+
487
+ - ***memory_graph.config.type_to_slicer*** : dict
488
+ - Maps each 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.
489
+
490
+ ### Temporary Configuration ###
491
+ In addition to the global configuration, a temporary configuration can be set for a single `show()`, `render()`, or `d()` call to change the colors, orientation, and slicer. This example highlights a particular list element in red, gives it a horizontal orientattion, and overwrites the default slicer for lists:
492
+
493
+ ```python
494
+ import memory_graph
495
+ from memory_graph.Slicer import Slicer
496
+
497
+ data = [ list(range(20)) for i in range(1,5)]
498
+ highlight = data[2]
499
+
500
+ memory_graph.show( locals(),
501
+ colors = {id(highlight): "red" }, # set color to "red"
502
+ vertical_orientations = {id(highlight): False }, # set horizontal orientation
503
+ slicers = {id(highlight): Slicer()} # set no slicing
504
+ )
505
+ ```
506
+ ![highlight.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/highlight.png)
507
+
508
+ ## 6. Extensions ##
509
+ Different extension are available for types from Python packages.
510
+
511
+ ### Numpy ###
512
+ Numpy types `arrray` and `matrix` and `ndarray` can be graphed with the "memory_graph.extension_numpy" extension:
513
+
514
+ ```python
515
+ from memory_graph import d
516
+ import numpy as np
517
+ import memory_graph.extension_numpy
518
+
519
+ array = np.array([1.1, 2, 3, 4, 5])
520
+ matrix = np.matrix([[i*20+j for j in range(20)] for i in range(20)])
521
+ ndarray = np.random.rand(20,20)
522
+ d()
523
+ ```
524
+ ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/extension_numpy.png)
525
+
526
+ ### Pandas ###
527
+ Pandas types `Series` and `DataFrame` can be graphed with the "memory_graph.extension_pandas" extension:
528
+
529
+ ```python
530
+ from memory_graph import d
531
+ import pandas as pd
532
+ import memory_graph.extension_pandas
533
+
534
+ series = pd.Series( [i for i in range(20)] )
535
+ dataframe1 = pd.DataFrame({ "calories": [420, 380, 390],
536
+ "duration": [50, 40, 45] })
537
+ dataframe2 = pd.DataFrame({ 'Name' : [ 'Tom', 'Anna', 'Steve', 'Lisa'],
538
+ 'Age' : [ 28, 34, 29, 42],
539
+ 'Length' : [ 1.70, 1.66, 1.82, 1.73] },
540
+ index=['one', 'two', 'three', 'four']) # with row names
541
+ d()
542
+ ```
543
+ ![extension_pandas.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/extension_pandas.png)
544
+
545
+ ## 7. Jupyter Notebook ##
546
+
547
+ In Jupyter Notebook `locals()` has additional variables that cause problems in the graph, use `memory_graph.locals_jupyter()` to get the local variables with these problematic variables filtered out. Use `memory_graph.get_call_stack_jupyter()` to get the whole call stack with these variables filtered out.
548
+
549
+ See for example [jupyter_example.ipynb](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.ipynb).
550
+ ![jupyter_example.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.png)
551
+
552
+
553
+ ## 8. Troubleshooting ##
554
+
555
+ - Adobe Acrobat Reader [doesn't refresh a PDF file](https://superuser.com/questions/337011/windows-pdf-viewer-that-auto-refreshes-pdf-when-compiling-with-pdflatex) when it changes on disk and blocks updates which results in an `Could not open 'somefile.pdf' for writing : Permission denied` error. One solution is to install a PDF reader that does refresh ([Evince](https://www.fosshub.com/Evince.html) for example) and set it as the default PDF reader. Another solution is to `render()` the graph to a different output format and open it manually.
556
+
557
+ - 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.
558
+
559
+
560
+