77
88
99class Visualiser (object ):
10+ # Total number of nodes
1011 node_count = 0
1112 graph = pydot .Dot (graph_type = "digraph" )
13+ # To track function call numbers
14+ stack = []
1215
13- def __init__ (self , ignore_args , show_argument_name = True , show_return_value = True ):
16+ def __init__ (self , ignore_args = None , show_argument_name = True , show_return_value = True ):
17+ #If enabled shows keyword arguments ordered by keys
1418 self .show_argument_name = show_argument_name
19+ # If enables shows the return value at every nodes
1520 self .show_return_value = show_return_value
16- self .ignore_args = ignore_args
21+
22+ # Argument string that are to be ignored in diagram
23+ if ignore_args is None :
24+ self .ignore_args = ['node_num' ]
25+ else :
26+ self .ignore_args = ['node_num' ] + ignore_args
27+
1728
1829 @classmethod
1930 def write_image (cls , filename = "out.png" ):
@@ -57,9 +68,16 @@ def extract_signature_label_arg_string(self, *args, **kwargs):
5768 def __call__ (self , fn ):
5869 @wraps (fn )
5970 def wrapper (* args , ** kwargs ):
71+
72+ # Increment total number of nodes when a call is made
73+ self .node_count += 1
74+
75+ # Update kwargs by adding dummy keyword node_num which helps to uniquely identify each node
76+ kwargs .update ({'node_num' : self .node_count })
6077 # Order all the keyword arguments
6178 kwargs = OrderedDict (sorted (kwargs .items ()))
6279
80+
6381 """Details about current Function"""
6482 # Get signature and label arguments strings for current function
6583 current_function_argument_string , current_function_label_argument_string = self .extract_signature_label_arg_string (* args , ** kwargs )
@@ -85,6 +103,11 @@ def wrapper(*args, **kwargs):
85103 # Extract only those locals that are in arguments
86104 caller_function_kwargs = {key : value for key , value in caller_function_locals .items () if key in caller_function_argument_names }
87105
106+ # If the nodes has parent node get node_num from parent node
107+ if self .stack :
108+ caller_function_kwargs .update ({'node_num' : self .stack [- 1 ]})
109+ caller_function_kwargs = OrderedDict (sorted (caller_function_kwargs .items ()))
110+
88111 caller_function_argument_string , caller_function_label_argument_string = self .extract_signature_label_arg_string (** caller_function_kwargs )
89112
90113 # Caller Function
@@ -98,8 +121,16 @@ def wrapper(*args, **kwargs):
98121 if caller_function_name == '<module>' :
99122 print (f"Drawing for { current_function_signature } " )
100123
124+ # Push node_count to stack
125+ self .stack .append (self .node_count )
126+ # Before actual function call delete keyword 'node_num' from kwargs
127+ del kwargs ['node_num' ]
128+ # Return after function call
101129 result = fn (* args , ** kwargs )
102130
131+ # Pop from tha stack after returning
132+ self .stack .pop ()
133+
103134 # If show_return_value flag is set, display the result
104135 if self .show_return_value :
105136 current_function_label += f" => { result } "
@@ -116,26 +147,4 @@ def wrapper(*args, **kwargs):
116147 edge = pydot .Edge (parent_node , child_node )
117148 self .graph .add_edge (edge )
118149 return result
119- return wrapper
120-
121- @Visualiser (ignore_args = ["node_num" ])
122- def fib (n , node_num ):
123- if n <= 1 :
124- return n
125- Visualiser .node_count += 1
126- left = fib (n = n - 1 , node_num = Visualiser .node_count )
127-
128- Visualiser .node_count += 1
129- right = fib (n = n - 2 , node_num = Visualiser .node_count )
130- return left + right
131-
132- def main ():
133- # Call function
134- print (fib (n = 6 , node_num = 0 ))
135-
136- # Save recursion tree to a file
137- Visualiser .write_image ("fibonacci.png" )
138-
139-
140- if __name__ == "__main__" :
141- main ()
150+ return wrapper
0 commit comments