Skip to content

Commit 6de93cb

Browse files
committed
Completition of 09
1 parent 906b3bd commit 6de93cb

File tree

2 files changed

+353
-2
lines changed
  • Concepts/09_Scopes_Closures_and_Decorators

2 files changed

+353
-2
lines changed

Concepts/09_Scopes_Closures_and_Decorators/07_Dec_Applications/source.py

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ def my_func(s):
5353
#---- Exmaple-1 ----
5454
from fractions import Fraction
5555
f = Fraction(2, 3)
56-
print('---- Decorating Class ----')
56+
print('\n\n---- Decorating Class ----')
57+
print('---- Example-1 ----')
5758
print(f.denominator)
5859
print(f.numerator)
5960
# Fraction.is_integer = lambda self: self.denominator == 1
@@ -95,7 +96,84 @@ def say_hi(self):
9596
return 'Hello {}'.format(self.name)
9697

9798
p = Person('Silver', 1999)
99+
print('---- Example-2 ----')
98100
p.debug()
99101

100102
# As from the above two example we can see that in first exmaple rather than using decorator, we can directly add the property,
101-
# while when are needed to reuse it we can make a decorator like in exmple-2.
103+
# while when are needed to reuse it we can make a decorator like in example-2. Since, debug_info can be used for many different classes.
104+
105+
@debug_info
106+
class Automobile:
107+
def __init__(self, make, model, year, top_speed):
108+
self.make = make
109+
self.model = model
110+
self.year = year
111+
self.top_speed = top_speed
112+
self._speed = 0
113+
114+
@property
115+
def speed(self):
116+
return self._speed
117+
118+
@speed.setter
119+
def speed(self, new_speed):
120+
if new_speed > self.top_speed:
121+
raise ValueError('Speed cannot exceed top_speed.')
122+
else:
123+
self._speed = new_speed
124+
125+
favourite = Automobile('Ford', 'Model T', 1908, 45)
126+
print(favourite.debug())
127+
# favourite.speed = 50 --> will give you the ValueError - Speed cannot exceed top_speed.
128+
favourite.speed = 40
129+
print(favourite.speed) # Returning speed property that is in-turn the self._speed
130+
131+
#---- Example-3 ----
132+
# - Monkey Patching the ordering method in our class with the decorator `total_ordering` that is provided by the Python
133+
from math import sqrt
134+
135+
# complete_ordering - our equivalent of total_ordering decorator - but is not such a good python code to be used
136+
# and is just for reference.
137+
# def complete_ordering(cls):
138+
# if '__eq__' in dir(cls) and '__lt__' in dir(cls):
139+
# cls.__le__ = lambda self, other: self < other or self == other
140+
# cls.__ge__ = lambda self, other: not(self < other) and not(self == other)
141+
# cls.__gt__ = lambda self, other: not(self < other)
142+
# return cls
143+
144+
from functools import total_ordering
145+
146+
@total_ordering
147+
class Point:
148+
def __init__(self, x ,y):
149+
self.x = x
150+
self.y = y
151+
152+
def __abs__(self):
153+
return sqrt(self.x ** 2 + self.y ** 2)
154+
155+
def __repr__(self):
156+
return 'Point({}, {})'.format(self.x, self.y)
157+
158+
def __lt__(self, other):
159+
if isinstance(other, Point):
160+
return abs(self) < abs(other)
161+
else:
162+
return False
163+
164+
p1, p2, p3 = Point(2,3), Point(2,3), Point(0,0)
165+
print('---- Example-3 ----')
166+
print('Abs: ', abs(p1))
167+
print('is')
168+
print(p1 is p2)
169+
print(p2 is p3)
170+
print('==')
171+
print(p1 == p2)
172+
print(p1 == p3)
173+
print('<')
174+
print(p2 < p1)
175+
print(p3 < p1)
176+
print('>=')
177+
print(p1 >= p2)
178+
179+
# For toatl_ordering to be working, only one of the >, <, >= or <= should be a defined method.
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
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

Comments
 (0)