memory-graph 0.3.82__tar.gz → 0.3.84__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 (41) hide show
  1. {memory_graph-0.3.82 → memory_graph-0.3.84}/PKG-INFO +59 -3
  2. {memory_graph-0.3.82 → memory_graph-0.3.84}/README.md +58 -2
  3. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/__init__.py +2 -2
  4. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/config.py +1 -0
  5. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/config_default.py +6 -0
  6. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/html_table.py +33 -15
  7. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/memory_to_nodes.py +4 -1
  8. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/node_key_value.py +4 -0
  9. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/node_linear.py +6 -1
  10. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/node_table.py +4 -0
  11. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/slices.py +6 -0
  12. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/slices_iterator.py +19 -4
  13. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph.egg-info/PKG-INFO +59 -3
  14. {memory_graph-0.3.82 → memory_graph-0.3.84}/pyproject.toml +1 -1
  15. {memory_graph-0.3.82 → memory_graph-0.3.84}/LICENSE.txt +0 -0
  16. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/call_stack.py +0 -0
  17. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/config_helpers.py +0 -0
  18. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/extension_numpy.py +0 -0
  19. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/extension_pandas.py +0 -0
  20. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/extension_torch.py +0 -0
  21. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/list_view.py +0 -0
  22. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/node_base.py +0 -0
  23. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/node_leaf.py +0 -0
  24. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/sequence.py +0 -0
  25. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/slicer.py +0 -0
  26. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/slices_table_iterator.py +0 -0
  27. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/test.py +0 -0
  28. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/test_max_graph_depth.py +0 -0
  29. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/test_memory_graph.py +0 -0
  30. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/test_memory_to_nodes.py +0 -0
  31. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/test_sequence.py +0 -0
  32. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/test_slicer.py +0 -0
  33. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/test_slices.py +0 -0
  34. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/test_slices_iterator.py +0 -0
  35. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph/utils.py +0 -0
  36. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph.egg-info/SOURCES.txt +0 -0
  37. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph.egg-info/dependency_links.txt +0 -0
  38. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph.egg-info/requires.txt +0 -0
  39. {memory_graph-0.3.82 → memory_graph-0.3.84}/memory_graph.egg-info/top_level.txt +0 -0
  40. {memory_graph-0.3.82 → memory_graph-0.3.84}/setup.cfg +0 -0
  41. {memory_graph-0.3.82 → memory_graph-0.3.84}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memory_graph
3
- Version: 0.3.82
3
+ Version: 0.3.84
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
@@ -366,7 +366,7 @@ a:[4, 3, 2, 1] b:(4, 3, 2) c:[4, 3, 2]
366
366
  ```
367
367
  This is because `b` is of immutable type 'tuple' so its value gets copied automatically when it is changed. And because the function is called with a copy of `c`, its original value is not changed by the function. The value of variable `a` is the only value of mutable type that is shared between the root stack frame **'0: \<module>'** and the **'1: add_one'** stack frame of the function call so only that variable is affected as a result of calling the function. The other changes remain confined to the local variables of the ```add_one()``` function.
368
368
 
369
- ## Function Call That Changes 'int' Value ##
369
+ ## Function Call Changes 'int' Value ##
370
370
  Even though `int` is an immutable type, so an `int` value can not be changed by directly passing it to a function, we can still change it by wrapping it in a mutable container.
371
371
 
372
372
  ```python
@@ -767,7 +767,58 @@ A sliding puzzle solver as a challenging example showing how memory_graph deals
767
767
 
768
768
  # Control Flow #
769
769
 
770
- Some examples where we focus on the flow of control of Python code.
770
+ Some examples where we focus on the flow of control and access to state in Python code.
771
+
772
+ ## Different Methods ##
773
+
774
+ This example shows the difference between 'instance', 'class' and 'static' methods:
775
+
776
+ ```python
777
+ class My_Class:
778
+
779
+ # class variables:
780
+ count = 0
781
+ unique_value = 0
782
+
783
+ def __init__(self):
784
+ print("__init__ called, initialize instance variables of object")
785
+ self.my_instance_variable = [] # create instance variable
786
+ My_Class.count += 1 # also change class variable
787
+
788
+ def my_instance_method(self):
789
+ print("my_instance_method called, access to instance variables via 'self'")
790
+ self.my_instance_variable.append(My_Class.unique_value)
791
+ My_Class.unique_value += 1 # also change class variable
792
+
793
+ @classmethod
794
+ def my_class_method(cls):
795
+ print("my_class_method called, access to class variables via 'cls', no instance variables")
796
+ print(f"{cls.count=}")
797
+
798
+ @staticmethod
799
+ def my_static_method():
800
+ print("my_static_method called, no 'self' or 'cls'")
801
+ print(f"{My_Class.count=}") # but still access to class variables
802
+
803
+ obj1 = My_Class()
804
+ obj2 = My_Class()
805
+
806
+ obj1.my_instance_method()
807
+ obj1.my_class_method()
808
+ obj1.my_static_method()
809
+
810
+ for _ in range(2):
811
+ obj1.my_instance_method()
812
+ obj2.my_instance_method()
813
+
814
+ print(f"{obj1.count=}") # reading class variable if no instance variable found
815
+ obj1.count = 100 # creating instance variable
816
+ print(f"{obj1.count=}") # now finding and reading instance variable
817
+ print(f"{My_Class.count}") # class variable still available via class name
818
+ print(f"{obj2.count=}") # obj2 still reads the class variable
819
+ ```
820
+
821
+ Run it in the [Memory Graph Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/different_methods.py&play).
771
822
 
772
823
  ## Inheritance ##
773
824
 
@@ -1048,6 +1099,9 @@ Different aspects of memory_graph can be configured. The default configuration c
1048
1099
 
1049
1100
  ## Functions ##
1050
1101
 
1102
+ - **mg.layout(horizontal: bool = None)**
1103
+ - Set graph layout to 'True' for horizontal, 'False' for vertical, or 'None' to toggle.
1104
+
1051
1105
  - **mg.dark_mode(b: bool = None)**
1052
1106
  - Set dark mode to 'True' or 'False', or 'None' to toggle.
1053
1107
 
@@ -1301,6 +1355,8 @@ mg.show(locals())
1301
1355
  ```
1302
1356
  ![html_str_example.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/html_str_example.png)
1303
1357
 
1358
+ ### Images
1359
+
1304
1360
  Including the `<IMG>` tag that allows for showing local image files only, but that currently doesn't work in the Memory Graph Web Debugger, that uses 'Viz.js' for graph rendering, unfortunately.
1305
1361
 
1306
1362
  ```python
@@ -346,7 +346,7 @@ a:[4, 3, 2, 1] b:(4, 3, 2) c:[4, 3, 2]
346
346
  ```
347
347
  This is because `b` is of immutable type 'tuple' so its value gets copied automatically when it is changed. And because the function is called with a copy of `c`, its original value is not changed by the function. The value of variable `a` is the only value of mutable type that is shared between the root stack frame **'0: \<module>'** and the **'1: add_one'** stack frame of the function call so only that variable is affected as a result of calling the function. The other changes remain confined to the local variables of the ```add_one()``` function.
348
348
 
349
- ## Function Call That Changes 'int' Value ##
349
+ ## Function Call Changes 'int' Value ##
350
350
  Even though `int` is an immutable type, so an `int` value can not be changed by directly passing it to a function, we can still change it by wrapping it in a mutable container.
351
351
 
352
352
  ```python
@@ -747,7 +747,58 @@ A sliding puzzle solver as a challenging example showing how memory_graph deals
747
747
 
748
748
  # Control Flow #
749
749
 
750
- Some examples where we focus on the flow of control of Python code.
750
+ Some examples where we focus on the flow of control and access to state in Python code.
751
+
752
+ ## Different Methods ##
753
+
754
+ This example shows the difference between 'instance', 'class' and 'static' methods:
755
+
756
+ ```python
757
+ class My_Class:
758
+
759
+ # class variables:
760
+ count = 0
761
+ unique_value = 0
762
+
763
+ def __init__(self):
764
+ print("__init__ called, initialize instance variables of object")
765
+ self.my_instance_variable = [] # create instance variable
766
+ My_Class.count += 1 # also change class variable
767
+
768
+ def my_instance_method(self):
769
+ print("my_instance_method called, access to instance variables via 'self'")
770
+ self.my_instance_variable.append(My_Class.unique_value)
771
+ My_Class.unique_value += 1 # also change class variable
772
+
773
+ @classmethod
774
+ def my_class_method(cls):
775
+ print("my_class_method called, access to class variables via 'cls', no instance variables")
776
+ print(f"{cls.count=}")
777
+
778
+ @staticmethod
779
+ def my_static_method():
780
+ print("my_static_method called, no 'self' or 'cls'")
781
+ print(f"{My_Class.count=}") # but still access to class variables
782
+
783
+ obj1 = My_Class()
784
+ obj2 = My_Class()
785
+
786
+ obj1.my_instance_method()
787
+ obj1.my_class_method()
788
+ obj1.my_static_method()
789
+
790
+ for _ in range(2):
791
+ obj1.my_instance_method()
792
+ obj2.my_instance_method()
793
+
794
+ print(f"{obj1.count=}") # reading class variable if no instance variable found
795
+ obj1.count = 100 # creating instance variable
796
+ print(f"{obj1.count=}") # now finding and reading instance variable
797
+ print(f"{My_Class.count}") # class variable still available via class name
798
+ print(f"{obj2.count=}") # obj2 still reads the class variable
799
+ ```
800
+
801
+ Run it in the [Memory Graph Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/different_methods.py&play).
751
802
 
752
803
  ## Inheritance ##
753
804
 
@@ -1028,6 +1079,9 @@ Different aspects of memory_graph can be configured. The default configuration c
1028
1079
 
1029
1080
  ## Functions ##
1030
1081
 
1082
+ - **mg.layout(horizontal: bool = None)**
1083
+ - Set graph layout to 'True' for horizontal, 'False' for vertical, or 'None' to toggle.
1084
+
1031
1085
  - **mg.dark_mode(b: bool = None)**
1032
1086
  - Set dark mode to 'True' or 'False', or 'None' to toggle.
1033
1087
 
@@ -1281,6 +1335,8 @@ mg.show(locals())
1281
1335
  ```
1282
1336
  ![html_str_example.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/html_str_example.png)
1283
1337
 
1338
+ ### Images
1339
+
1284
1340
  Including the `<IMG>` tag that allows for showing local image files only, but that currently doesn't work in the Memory Graph Web Debugger, that uses 'Viz.js' for graph rendering, unfortunately.
1285
1341
 
1286
1342
  ```python
@@ -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.82"
5
+ __version__ = "0.3.84"
6
6
  __author__ = 'Bas Terwijn'
7
7
 
8
8
  import memory_graph.memory_to_nodes as memory_to_nodes
@@ -26,7 +26,7 @@ if not hasattr(builtins, "mg"):
26
26
  builtins.mg = mg
27
27
 
28
28
  # re-exports for shorter names
29
- from .config_default import dark_mode, transparent_background, set_colors
29
+ from .config_default import layout, dark_mode, transparent_background, set_colors
30
30
  from .slicer import Slicer
31
31
  from .node_leaf import Node_Leaf
32
32
  from .node_linear import Node_Linear
@@ -29,6 +29,7 @@ type_to_string = { }
29
29
 
30
30
  type_to_node = { }
31
31
 
32
+ horizontal = False
32
33
  color_mode_dark = False
33
34
  transparent_background = False
34
35
  foreground_color = None
@@ -109,6 +109,12 @@ def dark_mode(dark = None):
109
109
  config.color_mode_dark = dark
110
110
  set_colors(config.color_mode_dark, config.transparent_background)
111
111
 
112
+ def layout(horizontal = None):
113
+ if horizontal is None:
114
+ config.horizontal = not config.horizontal
115
+ else:
116
+ config.horizontal = horizontal
117
+
112
118
  def reset():
113
119
 
114
120
  set_colors(config.color_mode_dark, config.transparent_background)
@@ -6,11 +6,6 @@ import memory_graph.config as config
6
6
  import memory_graph.config_helpers as config_helpers
7
7
  import memory_graph.utils as utils
8
8
 
9
- def html_table_frame(s, border, color, line_color='black', spacing=5):
10
- """ Helper function to add the HTML table frame to the string s setting the 'border' and 'color'. """
11
- return (f'<\n<TABLE BORDER="{border}" CELLBORDER="1" CELLSPACING="{spacing}" CELLPADDING="0" BGCOLOR="{color}" COLOR="{line_color}" PORT="table">\n <TR>' +
12
- s + '</TR>\n</TABLE>\n>')
13
-
14
9
  def format_string(value, quote_str):
15
10
  """ Helper function to format 'value' to be shown in the graph. We escape html characters and convert newlines to <BR/> tags. """
16
11
  to_string = config_helpers.get_to_string(value)
@@ -30,7 +25,7 @@ class HTML_Table:
30
25
  """
31
26
  Create an HTML_Table object.
32
27
  """
33
- self.html = ''
28
+ self.rows = [[]]
34
29
  self.add_new_line_flag = False
35
30
  self.is_empty = True
36
31
  self.col_count = 0
@@ -38,10 +33,24 @@ class HTML_Table:
38
33
  self.ref_count = 0
39
34
  self.max_col_count = 0
40
35
  self.edges = []
36
+ self.rows_reversed = False
37
+ self.columns_reversed = False
41
38
 
42
39
  def __repr__(self):
43
40
  """ Get the string representation of the HTML_Table object. """
44
- return self.html
41
+ return str(self.rows)
42
+
43
+ def add_row(self):
44
+ self.rows.append([])
45
+
46
+ def add_column(self, s):
47
+ self.rows[-1].append(s)
48
+
49
+ def reverse_rows(self, reversed=True):
50
+ self.rows_reversed = reversed
51
+
52
+ def reverse_columns(self, reversed=True):
53
+ self.columns_reversed = reversed
45
54
 
46
55
  def add_new_line(self):
47
56
  """ Set the 'add_new_line_flag' to add a new line to the table when adding the next table element. """
@@ -54,13 +63,13 @@ class HTML_Table:
54
63
  def check_add_new_line(self):
55
64
  """ Check if a new line should be added to the table, and if so add it and sets the 'add_new_line_flag' to False."""
56
65
  if self.add_new_line_flag:
57
- self.html += '</TR>\n <TR>'
66
+ self.add_row()
58
67
  self.add_new_line_flag = False
59
68
 
60
69
  def add_index(self, s):
61
70
  """ Add an index s to the table. """
62
71
  self.check_add_new_line()
63
- self.html += f'<TD BORDER="0"><font color="{config.index_color}">{str(s)}</font></TD>'
72
+ self.add_column(f'<TD BORDER="0"><font color="{config.index_color}">{str(s)}</font></TD>')
64
73
  self.col_count += 1
65
74
 
66
75
  def add_entry(self, node, nodes, child, id_to_slices, rounded=False, border=1, dashed=False, embed=False):
@@ -79,7 +88,7 @@ class HTML_Table:
79
88
  """ Helper function to add 'value' to the table. """
80
89
  self.check_add_new_line()
81
90
  r = ' STYLE="ROUNDED"' if rounded else ''
82
- self.html += f'<TD BORDER="{border}"{r}>{format_string(value, not rounded)}</TD>'
91
+ self.add_column(f'<TD BORDER="{border}"{r}>{format_string(value, not rounded)}</TD>')
83
92
  self.is_empty = False
84
93
  self.col_count += 1
85
94
 
@@ -87,7 +96,7 @@ class HTML_Table:
87
96
  """ Helper function to add a reference to the table. """
88
97
  self.check_add_new_line()
89
98
  r = ' STYLE="ROUNDED"' if rounded else ''
90
- self.html += f'<TD BORDER="{border}" PORT="ref{self.ref_count}"{r}> </TD>'
99
+ self.add_column(f'<TD BORDER="{border}" PORT="ref{self.ref_count}"{r}> </TD>')
91
100
  self.edges.append( (f'{node.get_name()}:ref{self.ref_count}',
92
101
  child.get_name(), dashed) )
93
102
  self.ref_count += 1
@@ -97,17 +106,25 @@ class HTML_Table:
97
106
  """ Helper function to add dots to the table. """
98
107
  self.check_add_new_line()
99
108
  r = 'STYLE="ROUNDED"' if rounded else ''
100
- self.html += f'<TD BORDER="{border}" {r}>...</TD>'
109
+ self.add_column(f'<TD BORDER="{border}" {r}>...</TD>')
101
110
  self.col_count += 1
102
111
 
112
+ def html_table_frame(self, border, color, line_color='black', spacing=5):
113
+ """ Helper function to add the HTML table frame to the string s setting the 'border' and 'color'. """
114
+ s = f'<\n<TABLE BORDER="{border}" CELLBORDER="1" CELLSPACING="{spacing}" CELLPADDING="0" BGCOLOR="{color}" COLOR="{line_color}" PORT="table">\n'
115
+ s += ''.join('<TR>' + ''.join(cell for cell in (reversed(row) if self.columns_reversed else row)) + '</TR>\n'
116
+ for row in (reversed(self.rows) if self.rows_reversed else self.rows))
117
+ s += '</TABLE>\n>'
118
+ return s
119
+
103
120
  def to_string(self, border=1, color='white', line_color='black'):
104
121
  """ Construct the HTML table string with the 'border' and 'color' settings. """
105
122
  if self.col_count == 0 and self.row_count == 0:
106
123
  if self.is_empty:
107
124
  self.add_value(utils.unquoted_str(''), border=0)
108
- return html_table_frame(self.html, border, color, line_color, spacing=0)
109
- return html_table_frame(self.html, border, color, line_color)
110
-
125
+ return self.html_table_frame(border, color, line_color, spacing=0)
126
+ return self.html_table_frame(border, color, line_color)
127
+
111
128
  def get_column(self):
112
129
  """ Get the number of columns in the table. """
113
130
  return self.col_count
@@ -128,6 +145,7 @@ if __name__ == '__main__':
128
145
  table = HTML_Table()
129
146
  rows = 4
130
147
  columns = 5
148
+ #table.reverse_columns()
131
149
  for r in range(rows):
132
150
  for c in range(columns):
133
151
  table.add_value(f'{c},{r}')
@@ -239,7 +239,8 @@ def build_graph(graphviz_graph, nodes, root_id, id_to_slices):
239
239
  if node_id in id_to_slices:
240
240
  slices = id_to_slices[node_id]
241
241
  if not slices is None:
242
- for index in slices:
242
+ slices_iter = slices.reverse_iter() if config.horizontal else iter(slices)
243
+ for index in slices_iter:
243
244
  child_id = id(children[index])
244
245
  build_graph_depth_first(graphviz_graph, nodes, child_id, id_to_slices, nodes_at_depth, subgraphed_nodes, depth+1)
245
246
  if not node.is_hidden_node():
@@ -268,5 +269,7 @@ def memory_to_nodes(data):
268
269
  graph_attr=graphviz_graph_attr,
269
270
  node_attr=graphviz_node_attr,
270
271
  edge_attr=graphviz_edge_attr)
272
+ if config.horizontal:
273
+ graphviz_graph.attr(rankdir='LR')
271
274
  build_graph(graphviz_graph, nodes, root_id, id_to_slices)
272
275
  return graphviz_graph
@@ -58,6 +58,10 @@ class Node_Key_Value(Node_Base):
58
58
  if slices is None:
59
59
  return
60
60
  vertical = self.is_vertical(nodes, slices, id_to_slices)
61
+ if config.horizontal:
62
+ vertical = not vertical
63
+ if vertical:
64
+ html_table.reverse_rows()
61
65
  if vertical:
62
66
  self.fill_html_table_vertical(html_table, nodes, slices, id_to_slices)
63
67
  else:
@@ -49,7 +49,12 @@ class Node_Linear(Node_Base):
49
49
  """
50
50
  if slices is None:
51
51
  return
52
- if self.is_vertical(nodes, slices, id_to_slices):
52
+ vertical = self.is_vertical(nodes, slices, id_to_slices)
53
+ if config.horizontal:
54
+ vertical = not vertical # flip orientation if horizontal layout is set
55
+ if vertical:
56
+ html_table.reverse_rows()
57
+ if vertical:
53
58
  self.fill_html_table_vertical(html_table, nodes, slices, id_to_slices)
54
59
  else:
55
60
  self.fill_html_table_horizontal(html_table, nodes, slices, id_to_slices)
@@ -6,6 +6,7 @@ from memory_graph.node_base import Node_Base
6
6
  from memory_graph.sequence import Sequence2D
7
7
  from memory_graph.list_view import List_View
8
8
 
9
+ import memory_graph.config as config
9
10
  import memory_graph.utils as utils
10
11
 
11
12
  class Node_Table(Node_Base):
@@ -44,6 +45,9 @@ class Node_Table(Node_Base):
44
45
  children_width = children_size[1]
45
46
  col_slices = slices.get_col_slices()
46
47
 
48
+ if config.horizontal:
49
+ html_table.reverse_rows()
50
+
47
51
  # use column indices for header row
48
52
  html_table.add_value(utils.unquoted_str(''), border=0)
49
53
  for coli in col_slices.table_iter(children_width):
@@ -107,6 +107,9 @@ class Slices1D(Slices):
107
107
  def __iter__(self):
108
108
  return Slices_Iterator1D(self)
109
109
 
110
+ def reverse_iter(self):
111
+ return Slices_Iterator1D(self, reverse=True)
112
+
110
113
  def has_index(self, index):
111
114
  insert = bisect.bisect_right([s[0] for s in self.slices], index)
112
115
  # print('insert:',insert,'index:',index,'slices:',self.slices)
@@ -150,6 +153,9 @@ class Slices2D(Slices):
150
153
  def __iter__(self):
151
154
  return Slices_Iterator2D(self)
152
155
 
156
+ def reverse_iter(self):
157
+ return Slices_Iterator2D(self, reverse=True)
158
+
153
159
  def has_index(self, index):
154
160
  i0, i1 = index
155
161
  return self.row_slices.has_index(i0) and self.col_slices.has_index(i1)
@@ -17,9 +17,9 @@ class Slices_Iterator(ABC):
17
17
 
18
18
  class Slices_Iterator1D(Slices_Iterator):
19
19
 
20
- def __init__(self, slices1d):
20
+ def __init__(self, slices1d, reverse=False):
21
21
  self.slices = slices1d
22
- self.gen = self.generate()
22
+ self.gen = self.reverse_generate() if reverse else self.generate()
23
23
 
24
24
  def __iter__(self):
25
25
  return self
@@ -30,14 +30,20 @@ class Slices_Iterator1D(Slices_Iterator):
30
30
  for i in range(slices[si][0], slices[si][1]):
31
31
  yield i
32
32
 
33
+ def reverse_generate(self):
34
+ slices = self.slices.get_slices()
35
+ for si in range(len(slices)-1, -1, -1):
36
+ for i in range(slices[si][1]-1, slices[si][0]-1, -1):
37
+ yield i
38
+
33
39
  def __next__(self):
34
40
  return next(self.gen)
35
41
 
36
42
  class Slices_Iterator2D(Slices_Iterator):
37
43
 
38
- def __init__(self, slices2d):
44
+ def __init__(self, slices2d, reverse=False):
39
45
  self.slices = slices2d
40
- self.gen = self.generate()
46
+ self.gen = self.reverse_generate() if reverse else self.generate()
41
47
 
42
48
  def __iter__(self):
43
49
  return self
@@ -51,5 +57,14 @@ class Slices_Iterator2D(Slices_Iterator):
51
57
  for col_i in range(col_slice[0], col_slice[1]):
52
58
  yield (row_i, col_i)
53
59
 
60
+ def reverse_generate(self):
61
+ row_slices = self.slices.get_row_slices().get_slices()
62
+ col_slices = self.slices.get_col_slices().get_slices()
63
+ for row_slice in reversed(row_slices):
64
+ for row_i in range(row_slice[1]-1, row_slice[0]-1, -1):
65
+ for col_slice in reversed(col_slices):
66
+ for col_i in range(col_slice[1]-1, col_slice[0]-1, -1):
67
+ yield (row_i, col_i)
68
+
54
69
  def __next__(self):
55
70
  return next(self.gen)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memory_graph
3
- Version: 0.3.82
3
+ Version: 0.3.84
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
@@ -366,7 +366,7 @@ a:[4, 3, 2, 1] b:(4, 3, 2) c:[4, 3, 2]
366
366
  ```
367
367
  This is because `b` is of immutable type 'tuple' so its value gets copied automatically when it is changed. And because the function is called with a copy of `c`, its original value is not changed by the function. The value of variable `a` is the only value of mutable type that is shared between the root stack frame **'0: \<module>'** and the **'1: add_one'** stack frame of the function call so only that variable is affected as a result of calling the function. The other changes remain confined to the local variables of the ```add_one()``` function.
368
368
 
369
- ## Function Call That Changes 'int' Value ##
369
+ ## Function Call Changes 'int' Value ##
370
370
  Even though `int` is an immutable type, so an `int` value can not be changed by directly passing it to a function, we can still change it by wrapping it in a mutable container.
371
371
 
372
372
  ```python
@@ -767,7 +767,58 @@ A sliding puzzle solver as a challenging example showing how memory_graph deals
767
767
 
768
768
  # Control Flow #
769
769
 
770
- Some examples where we focus on the flow of control of Python code.
770
+ Some examples where we focus on the flow of control and access to state in Python code.
771
+
772
+ ## Different Methods ##
773
+
774
+ This example shows the difference between 'instance', 'class' and 'static' methods:
775
+
776
+ ```python
777
+ class My_Class:
778
+
779
+ # class variables:
780
+ count = 0
781
+ unique_value = 0
782
+
783
+ def __init__(self):
784
+ print("__init__ called, initialize instance variables of object")
785
+ self.my_instance_variable = [] # create instance variable
786
+ My_Class.count += 1 # also change class variable
787
+
788
+ def my_instance_method(self):
789
+ print("my_instance_method called, access to instance variables via 'self'")
790
+ self.my_instance_variable.append(My_Class.unique_value)
791
+ My_Class.unique_value += 1 # also change class variable
792
+
793
+ @classmethod
794
+ def my_class_method(cls):
795
+ print("my_class_method called, access to class variables via 'cls', no instance variables")
796
+ print(f"{cls.count=}")
797
+
798
+ @staticmethod
799
+ def my_static_method():
800
+ print("my_static_method called, no 'self' or 'cls'")
801
+ print(f"{My_Class.count=}") # but still access to class variables
802
+
803
+ obj1 = My_Class()
804
+ obj2 = My_Class()
805
+
806
+ obj1.my_instance_method()
807
+ obj1.my_class_method()
808
+ obj1.my_static_method()
809
+
810
+ for _ in range(2):
811
+ obj1.my_instance_method()
812
+ obj2.my_instance_method()
813
+
814
+ print(f"{obj1.count=}") # reading class variable if no instance variable found
815
+ obj1.count = 100 # creating instance variable
816
+ print(f"{obj1.count=}") # now finding and reading instance variable
817
+ print(f"{My_Class.count}") # class variable still available via class name
818
+ print(f"{obj2.count=}") # obj2 still reads the class variable
819
+ ```
820
+
821
+ Run it in the [Memory Graph Web Debugger](https://memory-graph.com/#codeurl=https://raw.githubusercontent.com/bterwijn/memory_graph/refs/heads/main/src/different_methods.py&play).
771
822
 
772
823
  ## Inheritance ##
773
824
 
@@ -1048,6 +1099,9 @@ Different aspects of memory_graph can be configured. The default configuration c
1048
1099
 
1049
1100
  ## Functions ##
1050
1101
 
1102
+ - **mg.layout(horizontal: bool = None)**
1103
+ - Set graph layout to 'True' for horizontal, 'False' for vertical, or 'None' to toggle.
1104
+
1051
1105
  - **mg.dark_mode(b: bool = None)**
1052
1106
  - Set dark mode to 'True' or 'False', or 'None' to toggle.
1053
1107
 
@@ -1301,6 +1355,8 @@ mg.show(locals())
1301
1355
  ```
1302
1356
  ![html_str_example.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/html_str_example.png)
1303
1357
 
1358
+ ### Images
1359
+
1304
1360
  Including the `<IMG>` tag that allows for showing local image files only, but that currently doesn't work in the Memory Graph Web Debugger, that uses 'Viz.js' for graph rendering, unfortunately.
1305
1361
 
1306
1362
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memory_graph"
7
- version = "0.3.82"
7
+ version = "0.3.84"
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"}
File without changes
File without changes
File without changes