1+ #! /usr/bin/env python3 
2+ 
3+ ''' Python Script to understand the Python topics Scopes, Closures and Decorators ''' 
4+ 
5+ #--- Single Dispatch Generic Function ----  
6+ #   - Use case (where we have to format/handle the data for html) 
7+ 
8+ 
9+ 
10+ #---- Code-1 ---- 
11+ from  html  import  escape 
12+ 
13+ def  html_escape (arg ):
14+     return  escape (str (arg ))
15+ 
16+ def  html_int (a ):
17+     return  '{}(<i>{}</i>)' .format (a , str (hex (a ))) 
18+ 
19+ def  html_real (a ):
20+     return  '{:.2f}' .format (round (a , 2 ))
21+ 
22+ def  html_str (s ):
23+     return  html_escape (s ).replace ('\n ' , '<br/>\n ' )
24+ 
25+ def  html_list (l ):
26+     items  =  ('<li>{}</li>' .format (html_escape (item )) for  item  in  l )
27+     return  '<ul>\n '  +  '\n ' .join (items ) +  '</ul' 
28+ 
29+ def  html_dict (d ):
30+     items  =  ('<li>{}={}</li>' .format (k , v ) for  k , v  in  d .items ())
31+     return  '<ul>\n '  +  '\n ' .join (items ) +  '</ul>' 
32+ 
33+ print ('---- Code-1 ----' )
34+ print (html_str ('''this is 
35+ multi-line strings with special characters: 10 < 100 ''' ))
36+ print (html_int (255 ))
37+ print (html_escape (3 + 10j ))
38+ 
39+ # Single Dispatcher Fucntion (but is not generic) 
40+ def  htmlize (arg ):
41+     from  decimal  import  Decimal 
42+ 
43+     if  isinstance (arg , int ):
44+         return  html_int (arg )
45+     elif  isinstance (arg , float ) or  isinstance (arg , Decimal ):
46+         return  html_real (arg )
47+     elif  isinstance (arg , str ):
48+         return  html_str (arg )
49+     elif  isinstance (arg , list ) or  isinstance (arg , tuple ):
50+         return  html_list (arg )
51+     elif  isinstance (arg , dict ):
52+         return  html_dict (arg )
53+     else :
54+         return  html_escape (arg )
55+ 
56+ print (htmlize (100 ))
57+ print (htmlize (['Python rocks!!!' , (100 , 200 , 300 ), 100 ]))
58+ # Now the above implementation for our use case, gives us a output that's little off... as we have passed the list 
59+ # as a arg, which is is received by the htmlize and the hmtl_list is called which is escaping the contents of the list. 
60+ # But we want is, that the html_list function will again check inside the list taht is passed as arg so for that, we can use 
61+ # htlmize fucntion in place of html_escape in line-22. 
62+ 
63+ # Note: In python, we can reference or call a function (say f-2) in a body of another fucntion (f-1) even if that f-2 is not defined yet. 
64+ # And it is toatally fine as long as, the f-2 function will be created or defined before the the another function f-1 is beign called. 
65+ 
66+ del  html_list 
67+ del  html_dict 
68+ del  htmlize 
69+ 
70+ 
71+ 
72+ #---- Code-2 ---- 
73+ # So the improved version of above code is: 
74+ def  html_list (l ):
75+     items  =  ('<li>{}</li>' .format (htmlize (item )) for  item  in  l )
76+     return  '<ul>\n '  +  '\n ' .join (items ) +  '\n </ul' 
77+ 
78+ def  html_dict (d ):
79+     items  =  ('<li>{}={}</li>' .format (html_escape (k ), htmlize (v )) for  k , v  in  d .items ())
80+     return  '<ul>\n '  +  '\n ' .join (items ) +  '\n </ul>' 
81+ 
82+ def  htmlize (arg ):
83+     from  decimal  import  Decimal 
84+ 
85+     if  isinstance (arg , int ):
86+         return  html_int (arg )
87+     elif  isinstance (arg , float ) or  isinstance (arg , Decimal ):
88+         return  html_real (arg )
89+     elif  isinstance (arg , str ):
90+         return  html_str (arg )
91+     elif  isinstance (arg , list ) or  isinstance (arg , tuple ):
92+         return  html_list (arg )
93+     elif  isinstance (arg , dict ):
94+         return  html_dict (arg )
95+     else :
96+         return  html_escape (arg )
97+ 
98+ print ('\n ---- Code-2 ----' )
99+ print (htmlize (100 ))
100+ print (htmlize (['''Python  
101+ rocks!!!''' , (100 , 200 , 300 ), 100 ]))
102+ 
103+ # Now, if we want to format the set we have to add html_set and than edit the htmlize fucntion again. 
104+ # So every time we add some functionality we have to change the code and the code itself will loke  
105+ # much more untidy as it keeps on increasing in text-size. 
106+ 
107+ del  htmlize 
108+ 
109+ 
110+ 
111+ #---- Code-3 ---- 
112+ # So a much better approach is to maintain a dictionary... 
113+ def  html_set (arg ):
114+     return  html_list (arg )
115+ 
116+ def  htmlize (arg ):
117+     from  decimal  import  Decimal 
118+ 
119+     registry  =  {
120+         object : html_escape ,
121+         int : html_int ,
122+         float : html_real ,
123+         Decimal : html_real ,
124+         str : html_str ,
125+         list : html_list ,
126+         tuple : html_list ,
127+         dict : html_dict ,
128+         set : html_set 
129+         }
130+ 
131+     fn  =  registry .get (type (arg ), registry [object ])  # But in this case, if some class is inherited from the list 
132+                                                     # but since it is not list the case will fail 
133+     return  fn (arg )
134+ 
135+ print ('\n ---- Code-3 ----' )
136+ print (htmlize (100 ))
137+ print (htmlize ([1 , 2 , 3 ]))
138+ 
139+ # Now the code seems tidy but there is still the problem of re-writing the code every time we add a new function. 
140+ 
141+ del  html_escape 
142+ del  html_int 
143+ del  html_real 
144+ del  html_str 
145+ del  html_list 
146+ del  html_dict 
147+ del  html_set 
148+ del  htmlize 
149+ 
150+ 
151+ 
152+ #--- Code-4 --- 
153+ #   - Using a decorator/closure to make a single dispatcher `generic` function, so that instead of hard-coding we 
154+ #     can just inject the new fucntions. 
155+ from  decimal  import  Decimal 
156+ 
157+ def  single_dispatch (fn ):
158+     registry  =  {}
159+     registry [object ] =  fn 
160+     
161+     def  decorated_function (arg ):
162+         return  registry .get (type (arg ), registry [object ])(arg )
163+     
164+     # Decorator-Factory (or Paramterized decorator) 
165+     def  register (type_ ):
166+         # register_decorator is a decorator that does not modify the the functionality of other function (fn). 
167+         def  register_decorator (fn ):
168+             registry [type_ ] =  fn     # In place of this there can be a, register_decorated_function also which can have its own implementation. 
169+             return  fn     # We are simply returning the fucntion back. 
170+         return  register_decorator 
171+ 
172+     # Making the Decorator Factory, an attribute of a decorated functions, so we can access it. 
173+     decorated_function .register  =  register 
174+ 
175+     # In order to view the registry we will make a dispatch function 
176+     def  dispatch_registry (type_ ):
177+         return  registry .get (type_ , registry [object ])
178+ 
179+     # Making the getter fucntion as an attribute 
180+     decorated_function .dispatch_registry  =  dispatch_registry 
181+ 
182+     return  decorated_function 
183+ 
184+ @single_dispatch  
185+ def  htmlize (arg ):
186+     return  escape (str (arg ))
187+ 
188+ print ('\n ---- Code-4 ----' )
189+ print (htmlize ('1 < 100' ))
190+ print (htmlize (100 ))
191+ 
192+ # Calling a decorator factory (register) and getting a decorator back (register_decorator) from inside a decorated function (htmlize). 
193+ from  numbers  import  Integral     # Integral is for all non-decimal number types (including - int, Bool, etc.)  
194+ @htmlize .register (Integral ) 
195+ def  html_integral (a ):
196+     return  '{}(<i>{}<i>)' .format (a , str (hex (a )))
197+ 
198+ from  numbers  import  Real         # Real is for all real numbers types (including - flaot, Decimal, Fraction, etc.)  
199+ @htmlize .register (Real ) 
200+ def  html_real (a ):
201+     return  '{:.2f}' .format (round (a , 2 ))
202+ 
203+ @htmlize .register (str ) 
204+ def  html_str (s ):
205+     return  escape (s ).replace ('\n ' , '<br/>\n ' )
206+ 
207+ from  collections .abc  import  Sequence 
208+ @htmlize .register (Sequence ) 
209+ def  html_sequence (l ):
210+     items  =  ('<li>{}</li>' .format (htmlize (item )) for  item  in  l )
211+     return  '<ul>\n '  +  '\n ' .join (items ) +  '</ul' 
212+ 
213+ @htmlize .register (dict ) 
214+ def  html_dict (d ):
215+     items  =  ('<li>{}={}</li>' .format (html_escape (k ), htmlize (v )) for  k , v  in  d .items ())
216+     return  '<ul>\n '  +  '\n ' .join (items ) +  '\n </ul>' 
217+ 
218+ print ('...After Registering...' )
219+ print (htmlize .dispatch_registry (int ))
220+ print (htmlize (100 ))
221+ 
222+ # So, if we go for more generic approach then there can be multiple bugs related to our program as our code is also being getting complex. 
223+ # From the above codes, we got a understanding about the the working of a single_dispatch_generic_function and now we can directly implement 
224+ # the Python version of it. 
225+ 
226+ del  htmlize 
227+ 
228+ 
229+ 
230+ #---- Code-5 ---- 
231+ from  functools  import  singledispatch 
232+ 
233+ @singledispatch  
234+ def  htmlize (arg ):
235+     return  escape (str (arg ))
236+ 
237+ print ('\n ---- Code-5 ----' )
238+ print (htmlize .registry )
239+ print (htmlize .dispatch (str ))
240+ 
241+ @htmlize .register (Integral ) 
242+ def  html_integral (a ):
243+     return  '{}(<i>{}<i>)' .format (a , str (hex (a )))
244+ 
245+ print (htmlize .dispatch (int ))
246+ print (htmlize .dispatch (bool ))
247+ print (htmlize .dispatch (Integral ))
248+ 
249+ @htmlize .register (Sequence ) 
250+ def  html_sequence (l ):
251+     items  =  ('<li>{}</li>' .format (htmlize (item )) for  item  in  l )
252+     return  '<ul>\n '  +  '\n ' .join (items ) +  '\n </ul' 
253+ 
254+ print (htmlize ([1 , 2 , 3 ]))
255+ print (htmlize ((1 , 2 , 3 )))
256+ 
257+ # print(htmlize('python')) 
258+ #   - But their is a problem: `str` is also a Sequence type. Here in the above code we will get a RecursionError. 
259+ #     This is because the 'python' is also a sequence we will get the items in it - 'p', 'y', 't', 'h', 'o', 'n' 
260+ #     which are also strings, so we will recurse again... and hence max-recursion-limit crosses and we get an error. 
261+ 
262+ print (htmlize .dispatch (list ))
263+ print (htmlize .dispatch (tuple ))
264+ print (htmlize .dispatch (str ))
265+ 
266+ # To remove the above bug, we will defined a specific str functionality 
267+ @htmlize .register (str ) 
268+ def  html_str (s ):
269+     return  escape (s ).replace ('\n ' , '<br/>\n ' )
270+ 
271+ print (htmlize .dispatch (str ))
272+ print (htmlize ('''python 
273+ rocks!!!''' ))
0 commit comments