updated contents from Atlas repo

This commit is contained in:
Luciano Ramalho
2014-10-14 14:26:55 -03:00
parent 40688c038d
commit 981d5bc473
157 changed files with 71134 additions and 1 deletions

33
decorators/average.py Normal file
View File

@@ -0,0 +1,33 @@
"""
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)
>>> avg.__closure__ # doctest: +ELLIPSIS
(<cell at 0x...: list object at 0x...>,)
>>> avg.__closure__[0].cell_contents
[10, 11, 12]
"""
DEMO = """
>>> avg.__closure__
(<cell at 0x107a44f78: list object at 0x107a91a48>,)
"""
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager

View File

@@ -0,0 +1,20 @@
"""
>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'num_items' referenced before assignment
"""
def make_averager():
num_items = 0
total = 0
def averager(new_value):
num_items += 1
total += new_value
return total / num_items
return averager

View File

@@ -0,0 +1,45 @@
"""
>>> avg = make_averager()
>>> other_avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
>>> avg.__code__.co_varnames
('new_value',)
>>> avg.__code__.co_freevars
('num_items', 'total')
>>> avg.__closure__ # doctest: +ELLIPSIS
(<cell at 0x...: int object at 0x...>, <cell at 0x...: int object at 0x...>)
>>> avg.__closure__[0].cell_contents
3
>>> avg.__closure__[1].cell_contents
33
>>> other_avg(5)
5.0
>>> other_avg(10)
7.5
>>> other_avg(15)
10.0
"""
DEMO = """
>>> avg.__closure__
(<cell at 0x10fd24f78: int object at 0x10f6d3db0>,
<cell at 0x10fd24d38: int object at 0x10f6d4170>)
"""
def make_averager():
num_items = 0
total = 0
def averager(new_value):
nonlocal num_items, total
num_items += 1
total += new_value
return total / num_items
return averager

View File

@@ -0,0 +1,39 @@
"""
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
>>> avg.__code__.co_varnames
('new_value',)
>>> avg.__code__.co_freevars
('ns',)
>>> avg.__closure__ # doctest: +ELLIPSIS
(<cell at 0x...: Namespace object at 0x...>,)
>>> avg.__closure__[0].cell_contents.__dict__
{'total': 33, 'num_items': 3}
"""
DEMO = """
>>> avg.__closure__
(<cell at 0x108df5980: Namespace object at 0x108e06790>,)
"""
class Namespace(object):
pass
def make_averager():
ns = Namespace()
ns.num_items = 0
ns.total = 0
def averager(new_value):
ns.num_items += 1
ns.total += new_value
return float(ns.total) / ns.num_items
return averager

21
decorators/average_oo.py Normal file
View File

@@ -0,0 +1,21 @@
"""
>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
"""
class Averager():
def __init__(self):
self.series = []
def __call__(self, new_value):
self.series.append(new_value)
total = sum(self.series)
return total/len(self.series)

View File

@@ -0,0 +1,34 @@
"""
>>> import functools
>>> avg = functools.partial(averager, series=[])
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
>>> avg.args
()
>>> avg.keywords
{'series': [10, 11, 12]}
>>> avg.func # doctest: +ELLIPSIS
<function averager at 0x...>
>>> avg.func.__code__.co_varnames
('new_value', 'series', 'total')
"""
DEMO = """
>>> avg.func
<function averager at 0x1010c5560>
>>> avg.func.__code__.co_varnames
('new_value',)
>>> avg.__code__.co_freevars
('num_items', 'total')
>>> avg.__closure__
"""
def averager(new_value, series):
series.append(new_value)
total = sum(series)
return float(total)/len(series)

28
decorators/average_py2.py Normal file
View File

@@ -0,0 +1,28 @@
"""
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0
>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)
>>> avg.__closure__ # doctest: +ELLIPSIS
(<cell at 0x...: list object at 0x...>,)
>>> avg.__closure__[0].cell_contents
[10, 11, 12]
"""
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return float(total)/len(series)
return averager

15
decorators/clockdeco.py Normal file
View File

@@ -0,0 +1,15 @@
# clockdeco.py
import time
def clock(func):
def clocked(*args):
t0 = time.time()
result = func(*args)
elapsed = time.time() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked

23
decorators/clockdeco2.py Normal file
View File

@@ -0,0 +1,23 @@
# clockdeco2.py
import time
import functools
def clock(func):
@functools.wraps(func)
def clocked(*args, **kwargs):
t0 = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - t0
name = func.__name__
arg_lst = []
if args:
arg_lst.append(', '.join(repr(arg) for arg in args))
if kwargs:
pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
arg_lst.append(', '.join(pairs))
arg_str = ', '.join(arg_lst)
print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
return result
return clocked

View File

@@ -0,0 +1,31 @@
# clockdec2o_demo.py
"""
>>> pythagoras(3, 4) # doctest: +ELLIPSIS
[0.0...s] pythagoras(3, 4) -> 5.0
5.0
>>> pythagoras(9, h=15) # doctest: +ELLIPSIS
[0.0...s] pythagoras(9, h=15) -> 12.0
12.0
"""
import time
import math
from clockdeco2 import clock
@clock
def pythagoras(a, b=None, h=None):
if b is None and h is None:
raise TypeError('must provide second leg (b) or hypotenuse (h)')
if h is None:
return math.sqrt(a*a + b*b)
else:
return math.sqrt(h*h - a*a)
if __name__=='__main__':
print('*' * 40, 'Calling pythagoras(3, 4)')
pythagoras(3, 4)
print('*' * 40, 'Calling pythagoras(9, h=15)')
pythagoras(9, h=15)

View File

@@ -0,0 +1,95 @@
"""
>>> f_empty()
[0.0...s] f_empty() -> None
>>> f_args('spam', 3)
[0.0...s] f_args('spam', 3) -> 'spamspamspam'
'spamspamspam'
>>> snooze(1234)
[1...s] snooze(1234) -> None
>>> average(1, 2, 3)
[0.0...s] average(1, 2, 3) -> 2.0
2.0
>>> average(*range(10**3))
[0.0...s] average(0, 1, ..., 999) -> 499.5
499.5
>>> factorial(10)
[0.000...s] factorial(1) -> 1
[0.000...s] factorial(2) -> 2
[0.000...s] factorial(3) -> 6
[0.000...s] factorial(4) -> 24
[0.000...s] factorial(5) -> 120
[0.000...s] factorial(6) -> 720
[0.000...s] factorial(7) -> 5040
[0.000...s] factorial(8) -> 40320
[0.000...s] factorial(9) -> 362880
[0.000...s] factorial(10) -> 3628800
3628800
>>> fibonacci(1)
[0.000...s] fibonacci(1) -> 1
1
>>> fibonacci(5)
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(0) -> 0
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(2) -> 1
[0.000...s] fibonacci(3) -> 2
[0.000...s] fibonacci(0) -> 0
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(2) -> 1
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(0) -> 0
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(2) -> 1
[0.000...s] fibonacci(3) -> 2
[0.000...s] fibonacci(4) -> 3
[0.000...s] fibonacci(5) -> 5
5
>>> f_kwargs(3, 5, d='spam', c='eggs')
[0.0...s] f_kwargs(3, 5, c='eggs', d='spam') -> 15
15
>>> f_args.__name__
'f_args'
>>> f_kwargs.__name__
'f_kwargs'
"""
import time
from clockdeco2 import clock
@clock
def f_empty():
pass
@clock
def f_args(a, b):
return a*b
@clock
def snooze(milis):
time.sleep(milis/1000)
@clock
def average(*args):
return sum(args) / len(args)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
@clock
def f_kwargs(a, b, c=1, d='eggs'):
from time import sleep
sleep(0.001)
return a*b
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)

View File

@@ -0,0 +1,18 @@
# clockdeco_demo.py
import time
from clockdeco import clock
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
if __name__=='__main__':
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
print('6! =', factorial(6))

View File

@@ -0,0 +1,22 @@
from clockdeco import clock
import time
@clock
def snooze(milis):
time.sleep(milis/1000)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
snooze(123)
print(factorial(6))
print(fibonacci(4))

View File

@@ -0,0 +1,20 @@
import functools
from clockdeco import clock
@functools.lru_cache()
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
print(factorial(6))
print(fibonacci(6))

View File

@@ -0,0 +1,40 @@
# clockdeco_param.py
"""
>>> snooze(.1) # doctest: +ELLIPSIS
[0.101...s] snooze(0.1) -> None
>>> clock('{name}: {elapsed}')(time.sleep)(.2) # doctest: +ELLIPSIS
sleep: 0.20...
>>> clock('{name}({args}) dt={elapsed:0.3f}s')(time.sleep)(.2)
sleep(0.2) dt=0.201s
"""
# BEGIN CLOCKDECO_PARAM
import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
def clock(fmt=DEFAULT_FMT): # <1>
def decorate(func): # <2>
def clocked(*_args): # <3>
t0 = time.time()
_result = func(*_args) # <4>
elapsed = time.time() - t0
name = func.__name__
args = ', '.join(repr(arg) for arg in _args) # <5>
result = repr(_result) # <6>
print(fmt.format(**locals())) # <7>
return _result # <8>
return clocked # <9>
return decorate # <10>
if __name__ == '__main__':
@clock() # <11>
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)
# END CLOCKDECO_PARAM

View File

@@ -0,0 +1,9 @@
import time
from clockdeco_param import clock
@clock('{name}: {elapsed}s')
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)

View File

@@ -0,0 +1,9 @@
import time
from clockdeco_param import clock
@clock('{name}({args}) dt={elapsed:0.3f}s')
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)

View File

@@ -0,0 +1,82 @@
"""
>>> f_empty()
[0.0...s] f_empty() -> None
>>> f_args('spam', 3)
[0.0...s] f_args('spam', 3) -> 'spamspamspam'
'spamspamspam'
>>> snooze(1234)
[1...s] snooze(1234) -> None
>>> average(1, 2, 3)
[0.0...s] average(1, 2, 3) -> 2.0
2.0
>>> average(*range(10**3))
[0.0...s] average(0, 1, ..., 999) -> 499.5
499.5
>>> factorial(10)
[0.000...s] factorial(1) -> 1
[0.000...s] factorial(2) -> 2
[0.000...s] factorial(3) -> 6
[0.000...s] factorial(4) -> 24
[0.000...s] factorial(5) -> 120
[0.000...s] factorial(6) -> 720
[0.000...s] factorial(7) -> 5040
[0.000...s] factorial(8) -> 40320
[0.000...s] factorial(9) -> 362880
[0.000...s] factorial(10) -> 3628800
3628800
>>> fibonacci(1)
[0.000...s] fibonacci(1) -> 1
1
>>> fibonacci(5)
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(0) -> 0
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(2) -> 1
[0.000...s] fibonacci(3) -> 2
[0.000...s] fibonacci(0) -> 0
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(2) -> 1
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(0) -> 0
[0.000...s] fibonacci(1) -> 1
[0.000...s] fibonacci(2) -> 1
[0.000...s] fibonacci(3) -> 2
[0.000...s] fibonacci(4) -> 3
[0.000...s] fibonacci(5) -> 5
5
>>> f_args.__name__
'clocked'
"""
import time
from clockdeco import clock
@clock
def f_empty():
pass
@clock
def f_args(a, b):
return a*b
@clock
def snooze(milis):
time.sleep(milis/1000)
@clock
def average(*args):
return sum(args) / len(args)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)

50
decorators/currency.py Normal file
View File

@@ -0,0 +1,50 @@
# currency.py
"""
>>> convert(1, 'BRL', 'USD')
0.4591
>>> convert(1, 'USD', 'BRL')
2.1784
>>> convert(1, 'EUR', 'USD')
1.3482
>>> convert(1, 'USD', 'EUR')
0.7417
>>> convert(1, 'EUR', 'BRL')
2.9369
>>> convert(1, 'BRL', 'EUR')
0.3405
>>> from functools import partial
>>> eur = partial(convert, cur_to='EUR')
>>> eur(1, 'USD')
0.7417
>>> eur(1, 'BRL')
0.3405
>>> eur2brl = partial(convert, cur_from='EUR', cur_to='BRL')
>>> eur2brl(100)
293.6864
>>> type(eur2brl)
<class 'functools.partial'>
"""
DEMO = """
>>> eur2brl.func
<function convert at 0x1010c5560>
>>> eur2brl.args, eur2brl.keywords
((), {'cur_from': 'EUR', 'cur_to': 'BRL'})
"""
rates = {'BRL': 2.17836,
'CAD': 1.03615,
'CNY': 6.10562,
'EUR': 0.74173,
'GBP': 0.62814,
'INR': 61.8685,
'JPY': 98.6002,
'USD': 1.0}
reference = rates['USD']
def convert(amount, cur_from, cur_to):
ref_amount = reference / rates[cur_from] * amount
return round(ref_amount * rates[cur_to], 4)

10
decorators/fibo_demo.py Normal file
View File

@@ -0,0 +1,10 @@
from clockdeco import clock
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
if __name__=='__main__':
print(fibonacci(6))

View File

@@ -0,0 +1,13 @@
import functools
from clockdeco import clock
@functools.lru_cache() # <1>
@clock # <2>
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
if __name__=='__main__':
print(fibonacci(6))

60
decorators/fibonacci.py Normal file
View File

@@ -0,0 +1,60 @@
# source: http://oeis.org/A000045
fibo_seq = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,
987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025,
121393, 196418, 317811, 514229, 832040, 1346269, 2178309,
3524578, 5702887, 9227465, 14930352, 24157817, 39088169]
from functools import lru_cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
@lru_cache()
def fibonacci2(n):
if n < 2:
return n
return fibonacci2(n-2) + fibonacci2(n-1)
def memoize(func):
'''simplest memoizing decorator'''
cache = {}
def memoized(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return memoized
def test():
for i, expected in enumerate(fibo_seq[:31]):
print(i, expected)
assert fibonacci(i) == expected
def chronograph():
global fibonacci
from time import time
t0 = time()
n = 32
res = fibonacci(n)
#res = [fibonacci(n) for n in range(30)]
t1 = time()
print(n, res, format(t1-t0, '0.6f'))
t0 = time()
res = fibonacci2(n)
#res = [fibonacci2(n) for n in range(30)]
t1 = time()
print(n, res, format(t1-t0, '0.6f'))
t0 = time()
fibonacci = memoize(fibonacci)
res = fibonacci(n)
#res = [fibonacci2(n) for n in range(30)]
t1 = time()
print(n, res, format(t1-t0, '0.6f'))
if __name__=='__main__':
#test()
chronograph()

49
decorators/generic.py Normal file
View File

@@ -0,0 +1,49 @@
r"""
htmlize(): generic function example
# BEGIN HTMLIZE_DEMO
>>> htmlize({1, 2, 3}) # <1>
'<pre>{1, 2, 3}</pre>'
>>> htmlize(abs)
'<pre>&lt;built-in function abs&gt;</pre>'
>>> htmlize('Heimlich & Co.\n- a game') # <2>
'<p>Heimlich &amp; Co.<br>\n- a game</p>'
>>> htmlize(42) # <3>
'<pre>42 (0x2a)</pre>'
>>> print(htmlize(['alpha', 66, {3, 2, 1}])) # <4>
<ul>
<li><p>alpha</p></li>
<li><pre>66 (0x42)</pre></li>
<li><pre>{1, 2, 3}</pre></li>
</ul>
# END HTMLIZE_DEMO
"""
# BEGIN HTMLIZE
from functools import singledispatch
import html
@singledispatch # <1>
def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@htmlize.register(str) # <2>
def _(text): # <3>
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content)
@htmlize.register(int) # <4>
def _(n):
return '<pre>{0} (0x{0:x})</pre>'.format(n)
@htmlize.register(list)
def _(a_list):
inner = '</li>\n<li>'.join(htmlize(item) for item in a_list)
return '<ul>\n<li>' + inner + '</li>\n</ul>'
# END HTMLIZE

63
decorators/local_demo.py Normal file
View File

@@ -0,0 +1,63 @@
"""
>>> f1(3)
>>> b = 8
>>> f1(3)
a = 3
b = 8
>>> f2(3)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'b' referenced before assignment
>>> f3(3)
a = 3
b = 7
b = 6
>>> b = -5
>>> ff = f4()
>>> ff(3)
a = 3
b = 11
b = 6
>>> print('b =', b)
b = -5
"""
def f1(a):
print('a =', a)
print('b =', b)
def f2(a):
print('a =', a)
print('b =', b)
b = a * 10
print('b =', b)
def f3(a):
global b
print('a =', a)
print('b =', b)
b = a * 10
print('b =', b)
def f3b(a):
nonlocal b
print('a =', a)
print('b =', b)
b = a * 10
print('b =', b)
def f4():
b = 11
def f5(a):
nonlocal b
print('a =', a)
print('b =', b)
b = a * 2
print('b =', b)
return f5
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)

View File

@@ -0,0 +1,31 @@
# BEGIN REGISTRATION
registry = [] # <1>
def register(func): # <2>
print('running register(%s)' % func) # <3>
registry.append(func) # <4>
return func # <5>
@register # <6>
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3(): # <7>
print('running f3()')
def main(): # <8>
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main() # <9>
# END REGISTRATION

View File

@@ -0,0 +1,16 @@
# BEGIN REGISTRATION_ABRIDGED
registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
print('running main()')
print('registry ->', registry)
f1()
# END REGISTRATION_ABRIDGED

View File

@@ -0,0 +1,29 @@
# BEGIN REGISTRATION_PARAM
registry = set() # <1>
def register(active=True): # <2>
def decorate(func): # <3>
print('running register(active=%s)->decorate(%s)'
% (active, func))
if active: # <4>
registry.add(func)
else: # <5>
if func in registry:
registry.remove(func)
return func # <6>
return decorate # <7>
@register(active=False) # <8>
def f1():
print('running f1()')
@register() # <9>
def f2():
print('running f2()')
def f3():
print('running f3()')
# END REGISTRATION_PARAM

View File

@@ -0,0 +1,27 @@
def d1(f):
def wrapped():
print('d1/wrapped')
return f()
return wrapped
def d2(f):
def wrapped():
print('d2/wrapped')
return f()
return wrapped
@d1
@d2
def f():
print('f')
f()
def g():
print('g')
g = d1(d2(g))
g()

View File

@@ -0,0 +1,113 @@
# strategy_best4.py
# Strategy pattern -- function-based implementation
# selecting best promotion from list of functions
# registered by a decorator
"""
>>> joe = Customer('John Doe', 0)
>>> ann = Customer('Ann Smith', 1100)
>>> cart = [LineItem('banana', 4, .5),
... LineItem('apple', 10, 1.5),
... LineItem('watermellon', 5, 5.0)]
>>> Order(joe, cart, fidelity)
<Order total: 42.00 due: 42.00>
>>> Order(ann, cart, fidelity)
<Order total: 42.00 due: 39.90>
>>> banana_cart = [LineItem('banana', 30, .5),
... LineItem('apple', 10, 1.5)]
>>> Order(joe, banana_cart, bulk_item)
<Order total: 30.00 due: 28.50>
>>> long_order = [LineItem(str(item_code), 1, 1.0)
... for item_code in range(10)]
>>> Order(joe, long_order, large_order)
<Order total: 10.00 due: 9.30>
>>> Order(joe, cart, large_order)
<Order total: 42.00 due: 42.00>
# BEGIN STRATEGY_BEST_TESTS
>>> Order(joe, long_order, best_promo)
<Order total: 10.00 due: 9.30>
>>> Order(joe, banana_cart, best_promo)
<Order total: 30.00 due: 28.50>
>>> Order(ann, cart, best_promo)
<Order total: 42.00 due: 39.90>
# END STRATEGY_BEST_TESTS
"""
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.price * self.quantity
class Order: # the Context
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion(self)
return self.total() - discount
def __repr__(self):
fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
# BEGIN STRATEGY_BEST4
promos = [] # <1>
def promotion(promo_func): # <2>
promos.append(promo_func)
return promo_func
@promotion # <3>
def fidelity(order):
"""5% discount for customers with 1000 or more fidelity points"""
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
@promotion
def bulk_item(order):
"""10% discount for each LineItem with 20 or more units"""
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
@promotion
def large_order(order):
"""7% discount for orders with 10 or more distinct items"""
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
def best_promo(order): # <4>
"""Select best discount available
"""
return max(promo(order) for promo in promos)
# END STRATEGY_BEST4