Nov 14, 2013
http://advancedpython.hasgeek.com/
def square(x):
return x*x
print square(4)
16
f = square
f
<function __main__.square>
square
<function __main__.square>
f(4)
16
def sum_of_squares(x, y):
return square(x) + square(y)
sum_of_squares(3, 4)
25
def cube(x): return x*x*x
def sum_of_cubes(x, y):
return cube(x) + cube(y)
sum_of_cubes(3, 4)
91
def fxy(f, x, y):
return f(x) + f(y)
fxy(square, 3, 4)
25
fxy(cube, 3, 4)
91
Problem Write a function diff_of
that takes a function f
and two numbers x, y as arguments and returns the difference of f(x)
and f(y)
.
def pow2(x): return 2**x
fxy(pow2, 3, 4)
24
# to compute square of 3+4, we can do:
a = 3 + 4
square(a)
49
# or
square(3+4)
49
can we do the samething with functions?
fxy(<function defination here>, 3, 4)
Python provides lambda expressions for doing that.
fxy(lambda x: 2**x, 3, 4)
24
fxy(lambda x: 4**x, 3, 4)
320
def is a statement and lamdba is an expression.
You can have only one expression in the body of lambda. Thats the limitation.
f = lambda x: 2**x
print f(10)
1024
sorted(["perl", "Python", "Java", "haskell", "c", "go"])
['Java', 'Python', 'c', 'go', 'haskell', 'perl']
How do I sort these by length of the value instead of the value it self?
The sorted
function takes an optional argument key
.
sorted(["perl", "Python", "Java", "haskell", "c", "go"], key=len)
['c', 'go', 'perl', 'Java', 'Python', 'haskell']
len('go')
2
How to sort these without considering the case?
names = ["perl", "Python", "Java", "haskell", "c", "go"]
def lower(s): return s.lower()
sorted(names, key=lower)
['c', 'go', 'haskell', 'Java', 'perl', 'Python']
# writing the same thing using lambda
sorted(names, key=lambda s: s.lower())
['c', 'go', 'haskell', 'Java', 'perl', 'Python']
def sub(x, y):
return x - y
print sub(5, 2)
print sub(5, y=2)
print sub(x=5, y=2)
print sub(y=2, x=5)
3 3 3 3
def incr(x):
return x+1
incr(4)
5
def incr(x, amount=1):
return x+amount
print incr(5)
print incr(5, 4)
6 9
incr(5, amount=4)
9
Problem Implement a function unique
that takes a list of values and return a list of unique ones among them. The function should take an optional argument key
, which is a function used to compare 2 values, just like how sorted works.
>>> unique(["foo", "FooBar", "Foo", "foo"])
["foo", "FooBar", "Foo"]
>>> unique(["foo", "FooBar", "Foo", "foo"], key=lambda x:x.lower())
["foo", "FooBar"]
def unique(values, key):
d = {}
for v in values:
d[key(v)] = v
return d.values()
unique(["foo", "FooBar", "Foo", "foo"], key=lambda x:x.lower())
['FooBar', 'foo']
How to make the key argument optional?
def unique(values, key=None):
d = {}
for v in values:
if key:
k = key(v)
else:
k = v
d[k] = v
return d.values()
unique(["foo", "FooBar", "Foo", "foo"])
['FooBar', 'foo', 'Foo']
def unique(values, key=None):
key = key or (lambda x:x)
d = {}
for v in values:
d[key(v)] = v
return d.values()
unique(["foo", "FooBar", "Foo", "foo"])
['FooBar', 'foo', 'Foo']
Default values are computed at the function defination time.
def append(x, values=[]):
values.append(x)
return values
print append(3, [1, 2])
print append(3)
print append(4)
[1, 2, 3] [3] [3, 4]
def f():
print "f"
return 1
print "before defining incr"
def incr(x, amount=f()):
print "incr", x, amount
return x + amount
print "after defining incr"
print incr(3)
print incr(3, 2)
print incr(3, 5)
before defining incr f after defining incr incr 3 1 4 incr 3 2 5 incr 3 5 8
How to handle default values when it should initialized to something like a list?
def append(x, values=None):
# The default value is now initialized
# on every call
if values is None:
values = []
values.append(x)
return values
print append(3, [1, 2])
print append(3)
print append(4)
[1, 2, 3] [3] [4]
x = 1
y = x
x = 2
print x, y
2 1
x = [1, 2]
y = x
x.append(3)
print x
print y
[1, 2, 3] [1, 2, 3]
x = [1, 2]
y = x
x = [1, 2, 3]
print x
print y
[1, 2, 3] [1, 2]
x = [1, 2]
y = [x, 5]
x.append(3)
print y
[[1, 2, 3], 5]
x = [1, 2]
y = [x, x]
x.append(3)
print y
[[1, 2, 3], [1, 2, 3]]
x = 0
y = 0
def f(x):
y = x*x
return y
f(2)
print x, y
0 0
y = 0
a = 10
def f(x):
y = x+a
return y
print f(2)
print y
12 0
y = 0
a = 10
def f(x):
print y
y = x+a
return y
print f(2)
print y
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-61-30ff2313577a> in <module>() 6 return y 7 ----> 8 print f(2) 9 print y <ipython-input-61-30ff2313577a> in f(x) 2 a = 10 3 def f(x): ----> 4 print y 5 y = x+a 6 return y UnboundLocalError: local variable 'y' referenced before assignment
y = 0
a = 10
def f(x):
global y
print y
y = x+a
return y
print f(2)
print y
0 12 12
def f(x):
x = x + 1
a = 1
f(a)
print a
1
def f(x):
x.append(0)
a = [1]
f(a)
print a
[1, 0]
def exp(a, n):
if n == 0:
return 1
else:
return a * exp(a, n-1)
print exp(2, 10)
1024
Write a function to flatten a nested list.
def flatten_list(values):
"""Flattens a nested list.
>>> flatten_list([1, 2, [3, 5], [6, [7]]])
[1, 2, 3, 4, 5, 6, 7]
"""
result = []
for v in values:
if isinstance(v, list):
result += flatten_list(v)
else:
result.append(v)
return result
flatten_list([1, 2, [3, 5], [6, [7]]])
[1, 2, 3, 5, 6, 7]
Let try to do that without recursion.
def flatten_list(values):
result = []
for v in values:
if isinstance(v, list):
for v2 in v:
result.append(v2)
else:
result.append(v)
return result
flatten_list([1, 2, [3, 5], [6, [7]]])
[1, 2, 3, 5, 6, [7]]
That not correct. It works for 2 levels.
When the data is recursive, the solution will be mostly recursive.
def json_encode(data):
if isinstance(data, int):
return str(data)
elif isinstance(data, str):
# will not work if str has quotes in it.
return '"' + data + '"'
elif isinstance(data, list):
return "[" + ", ".join([json_encode(d) for d in data]) + "]"
print json_encode(1)
print json_encode("hello")
print json_encode([1, "hello"])
print json_encode([1, "hello", [3, [4], 5]])
print json_encode({"x": [1, "hello"], "y": {"a": 2}})
1 "hello" [1, "hello"] [1, "hello", [3, [4], 5]] None
def f(x):
def adder(y):
return x + y
print adder(2)
f(3)
5
def make_adder(x):
def adder(y):
return x + y
return adder
add5 = make_adder(5)
add4 = make_adder(4)
print add5(3), add4(3)
8 7
More practical use case of this.
"hello".center(10)
' hello '
"hello".center(20)
' hello '
def make_centeralign(width):
def f(s):
return s.center(width)
return f
center10 = make_centeralign(10)
# The built-in utility in python standard library
# allows us to create a new function with some arguments
# fixed.
from functools import partial
import string
center10 = partial(string.center, width=10)
center10('hello')
' hello '
max(1, 2, 3, 4, 5)
5
def my_sum(*args):
print args
result = 0
for a in args:
result += a
return result
print my_sum(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5) 15
def log(level, *args):
print "[%s]" % level, " ".join(args)
log("INFO", "hello", "world")
[INFO] hello world
def info(*args):
log("INFO", *args)
info("hello", "world")
[INFO] hello world
def add(a, b):
return a + b
print add(1, 2)
args = [1, 2]
print add(*args)
3 3
keyword arguments
def render_template(name, **kwargs):
print "rendering template %s using %s as arguments" % (name, kwargs)
render_template("index", user="anand")
rendering template index using {'user': 'anand'} as arguments
def square(x):
print "square", 4
return x
def sum_of_squares(x, y):
print "sum_of_squares", x, y
return square(x) + square(y)
print sum_of_squares(3, 4)
sum_of_squares 3 4 square 4 square 4 7
%%file trace1.py
import os
def trace(f):
def wrapper(*args):
if os.getenv("DEBUG") == "true":
print f.__name__, args
return f(*args)
return wrapper
Overwriting trace1.py
%%file sq.py
from trace1 import trace
def square(x):
return x
square = trace(square)
def sum_of_squares(x, y):
return square(x) + square(y)
sum_of_squares = trace(sum_of_squares)
print sum_of_squares(3, 4)
Overwriting sq.py
!python sq.py
7
!DEBUG=true python sq.py
sum_of_squares (3, 4) square (3,) square (4,) 7
Problem Write a decorator logtime
, which prints how much time it look to execute the function.
@logtime
def square(x):
return x*x
print square(4)
Will output something like:
square(4) -- took 0.00000001 seconds
16
# hint: use time.time module
import time
t0 = time.time()
for i in range(1000000):
i*i
t1 = time.time()
print "took", t1-t0, "seconds"
took 0.249258995056 seconds
Can a decorator return something other than a function?
Yes, but very unusual.
def notadecor(f):
return 1
@notadecor
def square(x):
return x*x
print square
1
How does the decorator really work?
def debug(f):
print "decorator called with", f
def wrapper(x):
print "wrapper called with", x
print "calling", f.__name__, "with", x
result = f(x)
print f.__name__, "returned", result
print "returning", result
return result
print "returning wrapper function", wrapper
return wrapper
print "before def sqaure"
@debug
def square(x):
print "square called with", x
return x*x
print "after def sqaure"
print
print square(4)
before def sqaure decorator called with <function square at 0x10199b8c0> returning wrapper function <function wrapper at 0x10199bb90> after def sqaure wrapper called with 4 calling square with 4 square called with 4 square returned 16 returning 16 16
Lets print square now and see what we get.
square
<function __main__.wrapper>
help(square)
Help on function wrapper in module __main__: wrapper(x)
def cube(x):
"""Computes cube of x"""
return x*x*x
help(cube)
Help on function cube in module __main__: cube(x) Computes cube of x
print cube.__name__
print cube.__doc__
cube Computes cube of x
from functools import wraps
def debug(f):
@wraps(f)
def wrapper(x):
print f.__name__, x
return f(x)
print f, wrapper
return wrapper
@debug
def square(x):
return x*x
print square(4)
print "---"
help(square)
<function square at 0x10199bed8> <function square at 0x10151d668> square 4 16 --- Help on function square in module __main__: square(x)
%%file trace2.py
indent = 0
def trace(f):
def wrapper(*args):
global indent
sargs = [str(a) for a in args]
print "| " * indent + "|-- %s(%s)" % (f.__name__, ", ".join(sargs))
indent += 1
result = f(*args)
indent -= 1
return result
return wrapper
Overwriting trace2.py
%%file sq2.py
from trace2 import trace
@trace
def multiply(a, b):
return a*b
@trace
def square(x):
return multiply(x, x)
@trace
def sum_of_squares(x, y):
return square(x) + square(y)
print sum_of_squares(3, 4)
Overwriting sq2.py
!python sq2.py
|-- sum_of_squares(3, 4) | |-- square(3) | | |-- multiply(3, 3) | |-- square(4) | | |-- multiply(4, 4) 25
%%file fib1.py
from trace2 import trace
@trace
def fib(n):
if n == 0 or n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
print fib(5)
Writing fib1.py
!python fib1.py
|-- 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) 8
%%file memoize.py
import functools
def memoize(f):
cache = {}
@functools.wraps(f)
def wrapper(*args):
if args not in cache:
cache[args] = f(*args)
return cache[args]
return wrapper
Overwriting memoize.py
%%file fib2.py
from trace2 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(5)
Overwriting fib2.py
!python fib2.py
|-- fib(5) | |-- fib(4) | | |-- fib(3) | | | |-- fib(2) | | | | |-- fib(1) | | | | |-- fib(0) 8
Problem Write a decorator ignore_exceptions
that catches and ignores the exceptions raised by the wrapped function. It should also print the exception before ignoring.
@ignore_exceptions
def wget(url):
return urllib2.urlopen(url).read()
wget("http://google.com/no-such-page")
should print something like:
Failed to download http://google.com/no-such-page : Not Found
Problem Write a decorator with_retry
that retries for 5 times with a delay of 1 second if the wrapped function raises exception. After 5 attempts, it print the error message and ignores the exception.
@with_retry
def wget(url):
return urllib2.urlopen(url).read()
wget("http://google.com/no-such-page")
Should print something like:
(1) Error Not Found. retrying...
(2) Error Not Found. retrying...
(3) Error Not Found. retrying...
(4) Error Not Found. retrying...
(5) Error Not Found. retrying...
Giving up.
If you look at the above example, it would be really nice to specify number of retries when decorating the function.
@with_retry(retries=5, delay=0.5)
def wget(url):
...
We may want to use similar pattern for memoize for timeouts.
@memoize(timeout=60)
def get_recent_posts(site):
...
Example: memoize with timeouts
import functools
import time
def memoize_with_timeout(timeout):
def decorator(f):
cache = {}
@functools.wraps(f)
def wrapper(*args):
entry = cache.get(args)
t = time.time()
# we are storing (value, time)
# so entry[0] will be the value
# and entry[1] will be the time
if not entry or entry[1]+timeout < t:
if not entry:
print "no entry found in cache, recomputing"
else:
print "entry is stale (%.2f seconds old), recomputing" % (t-entry[1])
cache[args] = f(*args), t
else:
print "found an entry(%0.2f seconds old), reusing" % (t-entry[1])
return cache[args][0]
return wrapper
return decorator
"""
@memoize_with_timeout(timeout=2)
def longrunning_func():
time.sleep(1)
return time.time()
def longrunning_func():
time.sleep(1)
return time.time()
decorator = memoize_with_timeout(timeout=2)
longrunning_func = decorator(longrunning_func)
"""
@memoize_with_timeout(.4)
def f():
return time.time()
for i in range(10):
value = f()
print i, value
time.sleep(.1)
no entry found in cache, recomputing 0 1384420653.14 found an entry(0.10 seconds old), reusing 1 1384420653.14 found an entry(0.20 seconds old), reusing 2 1384420653.14 found an entry(0.30 seconds old), reusing 3 1384420653.14 entry is stale (0.40 seconds old), recomputing 4 1384420653.55 found an entry(0.10 seconds old), reusing 5 1384420653.55 found an entry(0.20 seconds old), reusing 6 1384420653.55 found an entry(0.30 seconds old), reusing 7 1384420653.55 entry is stale (0.41 seconds old), recomputing 8 1384420653.95 found an entry(0.10 seconds old), reusing 9 1384420653.95
There are some tricks to reduce the levels of nesting and make it easier to read.
import functools
import time
def memoize_with_timeout(f=None, timeout=0):
if f is None:
# The following can also be used:
# return functools.partial(memoize_with_timeout, timeout=timeout)
def decor(f):
return memoize_with_timeout(f, timeout=timeout)
return decor
cache = {}
@functools.wraps(f)
def wrapper(*args):
entry = cache.get(args)
t = time.time()
# we are storing (value, time)
# so entry[0] will be the value
# and entry[1] will be the time
if not entry or entry[1]+timeout < t:
if not entry:
print "no entry found in cache, recomputing"
else:
print "entry is stale (%.2f seconds old), recomputing" % (t-entry[1])
cache[args] = f(*args), t
else:
print "found an entry(%0.2f seconds old), reusing" % (t-entry[1])
return cache[args][0]
return wrapper
@memoize_with_timeout(timeout=.4)
def f():
return time.time()
for i in range(10):
value = f()
print i, value
time.sleep(.1)
no entry found in cache, recomputing 0 1384422586.23 found an entry(0.10 seconds old), reusing 1 1384422586.23 found an entry(0.20 seconds old), reusing 2 1384422586.23 found an entry(0.30 seconds old), reusing 3 1384422586.23 entry is stale (0.40 seconds old), recomputing 4 1384422586.64 found an entry(0.10 seconds old), reusing 5 1384422586.64 found an entry(0.20 seconds old), reusing 6 1384422586.64 found an entry(0.30 seconds old), reusing 7 1384422586.64 entry is stale (0.41 seconds old), recomputing 8 1384422587.04 found an entry(0.10 seconds old), reusing 9 1384422587.04
Problem: Write a decorator function with_retries
which takes retries
, delay
and ignore_error
as arguments. If ignore_error
is True
, it ignores the error after retries. If it is False, it re-raises the last exception.
@with_retries(retries=5, delay=0.5, ignore_error=True)
def wget(url):
return urllib2.urlopen(url).read()
Example: web framework
Lets try to build a simple web framework using decorators. It'll have all moving parts of a web framework except network.
%%file bicycle.py
"""The bicycle web framework
"""
# mapping from paths to functions
mapping = []
def route(path):
def decorator(f):
mapping.append((path, f))
return f
return decorator
def request(path):
for p, func in mapping:
if p == path:
return func()
print "404 Not Found"
Overwriting bicycle.py
%%file webapp.py
from bicycle import route, request
@route("/")
def index():
print "Welcome"
@route("/hello")
def hello():
print "hello"
if __name__ == "__main__":
request("/")
request("/hello")
request("/no-such-page")
Overwriting webapp.py
!python webapp.py
Welcome hello 404 Not Found
Problem Write a decorator before_request
that register a function to be called before each request.
@before_request
def header():
print "======"
print "HEADER"
print "======"
print
@route("/")
def index():
print "Welcome"
request("/")
should print:
======
HEADER
======
Welcome
Ideas to improve the framework.
Pass query string as keyword arguments to request func.
request("/login", username="joe", password="secret")
bicycle.input() when called from your app should return {"username": "joe", "password": "secret"}
request("/hello", name="joe", repeat="10")