Decorators and Functional Programming in Python¶

Derived from the notebook: http://anandology.com/nb/2014/decorators-demystified/ (Was a workshop in Pycon India '14)

In [1]:
def square(x):
return x*x

print(square(4))

16

In [2]:
print(square)

<function square at 0x7f8fdd684ae8>

In [3]:
f = square

In [4]:
print(f)

<function square at 0x7f8fdd684ae8>


They're called first-class objects

In [5]:
def sum_of_square(x, y):
return square(x) + square(y)

In [6]:
sum_of_square(3,4)

Out[6]:
25
In [7]:
def sum_of(f, x, y):
return f(x) + f(y)

sum_of(square, 2, 3)

Out[7]:
13
In [8]:
def mod3(x):
return x % 3

sum_of(mod3, 4, 5)

Out[8]:
3
In [9]:
sum_of(lambda x: x %3, 4, 5)

Out[9]:
3
In [10]:
mod4 = lambda x: x % 4

In [11]:
mod4(17)

Out[11]:
1
In [12]:
print(lambda x: x % 4)

<function <lambda> at 0x7f8fdce3a158>

In [13]:
max(["Python", "Haskell"], key=len)

Out[13]:
'Haskell'
In [14]:
print(len)

<built-in function len>

In [15]:
len("Python")

True
False

In [16]:
def incr(x, amount=1):
return x + amount

In [17]:
incr(4)

Out[17]:
5
In [18]:
incr(4, 2)

Out[18]:
6
In [19]:
def sub(x, y):
return x-y

In [20]:
sub(3, 5)

Out[20]:
-2
In [21]:
sub(x=3, y=5)

Out[21]:
-2
In [22]:
sub(y=5, x=3)

Out[22]:
-2
In [23]:
max(1,2,3,4,5)

Out[23]:
5
In [24]:
def f(*args):
print(args)

f(2,3,5)
f(8,1)

(2, 3, 5)
(8, 1)

In [25]:
def xprint(label, *args):
print(type(args))
for a in args:
print(label, a)

In [26]:
xprint("Info", 1,2,3)

<class 'tuple'>
Info 1
Info 2
Info 3


Problem Implement a function maximum that takes 2 values x and y and a key function as argument and finds the maximum by comparing key(x) and key(y).

>>> maximum(3, -4, abs)
-4
>>> maximum("java", "Python", lambda s: s.lower())
'Python'



Problem Write a function strjoin that takes a separator as first argument followed by variable number of strings to join with that separator.

>>> strjoin("-", "a", "b", "c")
"a-b-c"
In [27]:
def maximum(*args, key=lambda x: x):
best = args[0]
for i in args:
if (key(i) > key(best)):
best = i
return best

In [28]:
maximum('aloo', 'python', 'z', 'Haskell', key=len)

Out[28]:
'Haskell'
In [29]:
def strjoin(sep, *args):
return sep.join(args)

strjoin("-", "a", "b", "c")

Out[29]:
'a-b-c'
In [30]:
# Just like variable args, you can have variable keword args too, just use ** instead of *

def f(**kwargs):
print(kwargs)

In [31]:
f(x=1, y=2, abcd=3)

{'abcd': 3, 'y': 2, 'x': 1}

In [32]:
[int(x) for x in '2 3 4 5'.split()] # List comprehension

Out[32]:
[2, 3, 4, 5]
In [33]:
[(k,v) for k, v in {'title': 'IIIT-D', 'href': 'http://iiitd.ac.in'}.items()]

Out[33]:
[('href', 'http://iiitd.ac.in'), ('title', 'IIIT-D')]
In [34]:
"Hey %s" % ('jai')

Out[34]:
'Hey jai'

Problem Make a function render_tag(tagname, **attrs) that returns a html tag as a string

>>> render_tag("a", href="http://iiitd.ac.in", title="IIIT-D")
'<a href="http://iiitd.ac.in", title="IIIT-D">'
In [35]:
def render_tag(tagname, **attrs):
pairs = ['%s="%s"' % (k,v) for k, v in attrs.items()]
pairs_str = " ".join(pairs)
return "<%s %s>" % (tagname, pairs_str)

In [36]:
render_tag("a", href="http://iiitd.ac.in", title="IIIT-D")

Out[36]:
'<a href="http://iiitd.ac.in" title="IIIT-D">'
In [37]:
# Currying
return x + y

In [38]:
add5 = make_adder(5)

Out[38]:
7
In [39]:
add6 = make_adder(6)

Out[39]:
13
In [40]:
make_adder(8)(5)

Out[40]:
13

Decorators¶

In [41]:
%%file trace0.py

def trace(f):
def wrapper(*args):
print(f.__name__, args)
return f(*args)
return wrapper

Overwriting trace0.py

In [42]:
%%file sum.py

from trace0 import trace

@trace
def square(x):
# print("square", x)a
return x*x

#square = trace(square)

@trace
def sum_of_squares(x, y):
# print("sum_of_squares", x, y)
return square(x) + square(y)

#sum_of_squares = trace(sum_of_squares)

print(sum_of_squares(3,4))

Overwriting sum.py

In [43]:
!python sum.py

sum_of_squares (3, 4)
square (3,)
square (4,)
25

In [44]:
%%file trace1.py

level = 0

def trace(f):
def wrapper(*args):
global level
print("|  " * level + "|--", f.__name__, args)

level += 1
result = f(*args)
level -= 1

return result
return wrapper

Overwriting trace1.py

In [45]:
%%file fibo.py

from trace1 import trace
from memoize import memoize

@trace
def fib(n):
if n==0 or n==1:
return 1
else:
return fib(n-1) + fib(n-2)

print(fib(7))

Overwriting fibo.py

In [46]:
!python fibo.py

|-- fib (7,)
|  |-- fib (6,)
|  |  |-- fib (5,)
|  |  |  |-- fib (4,)
|  |  |  |  |-- fib (3,)
|  |  |  |  |  |-- fib (2,)
|  |  |  |  |  |  |-- fib (1,)
|  |  |  |  |  |  |-- fib (0,)
|  |  |  |  |  |-- fib (1,)
|  |  |  |  |-- fib (2,)
|  |  |  |  |  |-- fib (1,)
|  |  |  |  |  |-- fib (0,)
|  |  |  |-- fib (3,)
|  |  |  |  |-- fib (2,)
|  |  |  |  |  |-- fib (1,)
|  |  |  |  |  |-- fib (0,)
|  |  |  |  |-- fib (1,)
|  |  |-- fib (4,)
|  |  |  |-- fib (3,)
|  |  |  |  |-- fib (2,)
|  |  |  |  |  |-- fib (1,)
|  |  |  |  |  |-- fib (0,)
|  |  |  |  |-- fib (1,)
|  |  |  |-- fib (2,)
|  |  |  |  |-- fib (1,)
|  |  |  |  |-- fib (0,)
|  |-- fib (5,)
|  |  |-- fib (4,)
|  |  |  |-- fib (3,)
|  |  |  |  |-- fib (2,)
|  |  |  |  |  |-- fib (1,)
|  |  |  |  |  |-- fib (0,)
|  |  |  |  |-- fib (1,)
|  |  |  |-- fib (2,)
|  |  |  |  |-- fib (1,)
|  |  |  |  |-- fib (0,)
|  |  |-- fib (3,)
|  |  |  |-- fib (2,)
|  |  |  |  |-- fib (1,)
|  |  |  |  |-- fib (0,)
|  |  |  |-- fib (1,)
21

In [47]:
%%file memoize.py

def memoize(f):

cache = {}

def wrapper(*args):
if args not in cache:
cache[args] = f(*args)
return cache[args]
return wrapper

Overwriting memoize.py

In [48]:
%%file fibo1.py

from trace1 import trace
from memoize import memoize

@memoize
@trace
def fib(n):
if n==0 or n==1:
return 1
else:
return fib(n-1) + fib(n-2)

print(fib(7))

Overwriting fibo1.py

In [49]:
!python fibo1.py

|-- fib (7,)
|  |-- fib (6,)
|  |  |-- fib (5,)
|  |  |  |-- fib (4,)
|  |  |  |  |-- fib (3,)
|  |  |  |  |  |-- fib (2,)
|  |  |  |  |  |  |-- fib (1,)
|  |  |  |  |  |  |-- fib (0,)
21


Problem Write a function with_retries that continue to retry for 5 times if there is any exception raised in the function.

@with_retries
def wget(url):

wget("http://google.com/no-such-page")

Should print:

Failed to download, retrying...
Giving up!
In [50]:
%%file with_retries.py

import urllib.request

def with_retries(f, num):
def wrapper(*args):
for i in range(num):
try:
return f(*args)
except:
print("Failed")
print("Given up!")
return wrapper

#@with_retries
def wget(url):
return urllib.request.urlopen(url)

wget = with_retries(wget, 7)

Overwriting with_retries.py

In [51]:
!python with_retries.py

Failed
Failed
Failed
Failed
Failed
Failed
Failed
Given up!

In [52]:
%%file with_retries_adv.py

def with_retries(num):
def decor(f):
def wrapper(*args):
for i in range(num):
try:
return f(*args)
except:
print("failed")
print("given up!")
return wrapper
return decor

@with_retries(3)
def wget(url):
return urllib.request.urlopen(url)

# wget = with_retries(3)(wget)


Overwriting with_retries_adv.py

In [53]:
!python with_retries_adv.py

failed
failed
failed
given up!


Web framework - JUG (nano flask)¶

In [54]:
%%file jug.py

mapping = []

def route(path):
def decor(f):
mapping.append((path, f))
return decor

def request(path):
for p, func in mapping:
if p == path:
return func()
return "404"

def wsgifunc(env, start_response):
path = env['PATH_INFO']
start_response('200 OK', [("Content-Type", "text/plain")])
return [request(path).encode('utf-8')]

def run(port=8080):
from wsgiref.simple_server import make_server
server = make_server("0.0.0.0", port, wsgifunc)
server.serve_forever()

Overwriting jug.py

In [55]:
%%file hello.py

from jug import route

@route('/hello')
def hello():
return "Hello! :D"

@route('/bye')
def bye():
return "Good bye!"

Overwriting hello.py

In [56]:
%%file client.py
import hello
from jug import request, run
import sys

if "--web" in sys.argv:
run()
else:
print(request("/hello"))
print(request("/bye"))

Overwriting client.py

In [57]:
!python client.py

Hello! :D
Good bye!

In [59]:
!python client.py --web

127.0.0.1 - - [28/Oct/2015 21:49:49] "GET /hello HTTP/1.1" 200 9
^CTraceback (most recent call last):
File "client.py", line 6, in <module>
run()
File "/home/darkapex/decorators/jug.py", line 23, in run
server.serve_forever()
File "/usr/lib/python3.5/socketserver.py", line 237, in serve_forever