Advanced Python Training at Arcesium - Day 1

Nov 15-17, 2017 Vikrant Patil

These notes are available online at http://notes.pipal.in/2017/arcesium-oct-advpython/day1.html

© Pipal Academy LLP

Day 1 | Day 2 | Day 3

Quick recap

In [1]:
4 + 2.0 # basic data types
Out[1]:
6.0
In [2]:
4 ** 100
Out[2]:
1606938044258990275541962092341162602522202993782792835301376
In [3]:
5 / 2
Out[3]:
2.5
In [4]:
5 // 2
Out[4]:
2
In [5]:
"There" "are" "strings"
Out[5]:
'Therearestrings'
In [6]:
"you" + "can" + "add" + "strings"
Out[6]:
'youcanaddstrings'
In [7]:
"stings with ' quote"
Out[7]:
"stings with ' quote"
In [8]:
'string with " quite'
Out[8]:
'string with " quite'
In [9]:
multi = """
this is
multi
line
string
"""
In [10]:
print(multi)
this is
multi
line
string

In [11]:
digits = list(range(10))
In [12]:
digits
Out[12]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [13]:
digits[0]
Out[13]:
0
In [14]:
digits[9]
Out[14]:
9
In [15]:
digits[-1]
Out[15]:
9
In [16]:
digits[-2]
Out[16]:
8
In [17]:
digits[1:6] #sublist which starts at index 1 (inclusive) and ends at index 6(exlusive)
Out[17]:
[1, 2, 3, 4, 5]
In [18]:
digits[2:]
Out[18]:
[2, 3, 4, 5, 6, 7, 8, 9]
In [19]:
digits[:6]
Out[19]:
[0, 1, 2, 3, 4, 5]
In [20]:
digits[:-1] # all items except last
Out[20]:
[0, 1, 2, 3, 4, 5, 6, 7, 8]
In [21]:
digits[:] # copy 
Out[21]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [22]:
digits[2:7:2] # elements from index 2 till 7 with step of 2
Out[22]:
[2, 4, 6]
In [23]:
digits[::-1] # reverse
Out[23]:
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

All the indexing and slicing techniques work with strings too!

In [24]:
word = "madam"
In [25]:
word == word[::-1]
Out[25]:
True

List comprehensions

In [26]:
numbers = list(range(2,20,3))
In [27]:
numbers
Out[27]:
[2, 5, 8, 11, 14, 17]
In [28]:
[n*n for n in numbers]
Out[28]:
[4, 25, 64, 121, 196, 289]
In [29]:
[n*n*n for n in numbers]
Out[29]:
[8, 125, 512, 1331, 2744, 4913]
In [30]:
words = "some sentence with some words in it".split()
In [31]:
words
Out[31]:
['some', 'sentence', 'with', 'some', 'words', 'in', 'it']
In [33]:
[word.upper() for word in words]
Out[33]:
['SOME', 'SENTENCE', 'WITH', 'SOME', 'WORDS', 'IN', 'IT']
In [34]:
[i for i in range(20) if i%2 == 0] #even numbers
Out[34]:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
In [35]:
"it" is "not english"
Out[35]:
False
In [36]:
"hell" in "hello"
Out[36]:
True
In [37]:
person = {
    "name":"Alice",
    "email":"alice@example.com",
    "roles":["Admin", "Staff"]
}
In [38]:
person['name']
Out[38]:
'Alice'
In [39]:
person["email"]
Out[39]:
'alice@example.com'
In [40]:
[key for key in person.keys()]
Out[40]:
['name', 'email', 'roles']
In [41]:
[v for v in person.values() ]
Out[41]:
['Alice', 'alice@example.com', ['Admin', 'Staff']]
In [43]:
[(k,v) for k,v in person.items()]
Out[43]:
[('name', 'Alice'),
 ('email', 'alice@example.com'),
 ('roles', ['Admin', 'Staff'])]

Quicksort

In [45]:
def quicksort(numbers):
    def morethan(tail, pivot):
        return [x for x in tail if x>=pivot]
    
    def lessthan(tail, pivot):
        return [x for x in tail if x<pivot]
    
    if numbers:
        pivot = numbers[0]
        tail = numbers[1:]
        less = lessthan(tail, pivot)
        more = morethan(tail, pivot)
        return quicksort(less)+ [pivot] + quicksort(more)
    else:
        return []
In [47]:
quicksort([4,2,6,7,8,20,100,80,3])
Out[47]:
[2, 3, 4, 6, 7, 8, 20, 80, 100]

Example: a CSV parser

In [48]:
%%file data.csv
A1,B1,C1
A2,B2,C2
A3,B3,C3
A4,B4,C4
Writing data.csv
In [50]:
lines = open("data.csv").readlines()
In [51]:
lines
Out[51]:
['A1,B1,C1\n', 'A2,B2,C2\n', 'A3,B3,C3\n', 'A4,B4,C4']
In [52]:
[line.strip().split(",") for line in lines]
Out[52]:
[['A1', 'B1', 'C1'],
 ['A2', 'B2', 'C2'],
 ['A3', 'B3', 'C3'],
 ['A4', 'B4', 'C4']]
In [53]:
data = [line.strip().split(",") for line in lines]
In [54]:
data
Out[54]:
[['A1', 'B1', 'C1'],
 ['A2', 'B2', 'C2'],
 ['A3', 'B3', 'C3'],
 ['A4', 'B4', 'C4']]
In [56]:
data[-1][0] #last row first element
Out[56]:
'A4'

Functions

positional arguments

In [57]:
def cylinder_volume(radius, height):
    return 3.14*radius*radius*height
In [58]:
cylinder_volume(5,10)#radius first or height first?
Out[58]:
785.0
In [60]:
cylinder_volume(10,5)# if you give arguments in wrong order, result will be wrong..it
                    #is difficult to debug 
Out[60]:
1570.0

Named arguments

In [61]:
cylinder_volume(5, height=10)
Out[61]:
785.0
In [62]:
cylinder_volume(radius=5, height=10)
Out[62]:
785.0
In [63]:
cylinder_volume(radius=5,10)
  File "<ipython-input-63-b4b48b2b8bf2>", line 1
    cylinder_volume(radius=5,10)
                            ^
SyntaxError: positional argument follows keyword argument

Default arguments

In [67]:
import os
def location(name, home="/home/vikrant"):
    """
    returns virtual environment root directory
    """
    return os.path.sep.join([home, "usr","local", name])
In [68]:
location("jupyter")
Out[68]:
'/home/vikrant/usr/local/jupyter'
In [69]:
location("jupyter", home="/home/anand")
Out[69]:
'/home/anand/usr/local/jupyter'
In [70]:
import random
def number():
    return random.random()

def func(a, b=number()):
    print(a,b)
In [71]:
for i in range(5):
    func(i)
0 0.47348835265901446
1 0.47348835265901446
2 0.47348835265901446
3 0.47348835265901446
4 0.47348835265901446
In [72]:
def func(a, b=None):
    if not b:
        b = number()
    print(a,b)
In [73]:
for i in range(5):
    func(i)
0 0.422111237347372
1 0.5764367498966434
2 0.37272483219909747
3 0.5779499592241668
4 0.2583700099957633

Also make sure that you use only immutables as default arguments

In [74]:
def append(a, values=[]):
    values.append(a)
    return values
In [75]:
append("A")
Out[75]:
['A']
In [76]:
append("B")
Out[76]:
['A', 'B']
In [77]:
def append(a, values=None):
    if values is None:
        values = []
    values.append(a)
    return values
In [78]:
append("A")
Out[78]:
['A']
In [79]:
append("B")
Out[79]:
['B']

Only named arguments

In [81]:
def summation(values, *, initial=0):
    return initial + sum(values)
In [82]:
summation(range(10), initial=50)
Out[82]:
95
In [83]:
summation(range(10), 0)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-83-9f352b6fffe6> in <module>()
----> 1 summation(range(10), 0)

TypeError: summation() takes 1 positional argument but 2 were given

Variable number of arguments

In [85]:
def func(*args):
    pass
In [86]:
func(1)
In [87]:
func(1,2)
In [88]:
func(1,2,3,4,5)
In [89]:
def func(*args):
    print(args)
In [90]:
func(1,2,3,4,5)
(1, 2, 3, 4, 5)

problem: Write a function genericsum which sums up all arguments passed to it

>>> genericsum(1,1,1,1,1)
5
>>> genericsum(1,2,3)
6
In [91]:
",".join(["a","b"])
    
Out[91]:
'a,b'
In [92]:
def joinstrings(*args, sep=" "):
    alltogether = args[0]
    for word in args[1:]:
        alltogether = alltogether + sep + word
        
    return alltogether
In [96]:
joinstrings("join","these","strings","with","space")
Out[96]:
'join these strings with space'
In [99]:
joinstrings(["a","b","c"],["b","c"])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-99-87f935c53975> in <module>()
----> 1 joinstrings(["a","b","c"],["b","c"])

<ipython-input-92-8666e46d6c77> in joinstrings(sep, *args)
      2     alltogether = args[0]
      3     for word in args[1:]:
----> 4         alltogether = alltogether + sep + word
      5 
      6     return alltogether

TypeError: can only concatenate list (not "str") to list
In [100]:
 
In [ ]:
fun(1,2,3,4)
In [94]:
def make_person(name, **kwargs):
    person = {"name":name}
    for key,value in kwargs.items():
        person[key] = value
    return person
In [95]:
make_person(name="haskell",surname="curry",email="haskell@functional.expressions.com")
Out[95]:
{'email': 'haskell@functional.expressions.com',
 'name': 'haskell',
 'surname': 'curry'}

Unpacking the arguments

In [101]:
def fun(*args):
    print(args)
    print(*args)
In [103]:
fun(1,2,3,4)
(1, 2, 3, 4)
1 2 3 4
In [104]:
fun([1,2,3,4])
([1, 2, 3, 4],)
[1, 2, 3, 4]
In [105]:
def genericsum(*args):
    s = 0
    for item in args:
        s += item
    return s

def find_stats(*args):
    results = { 'sum':genericsum(*args),
               'mean':genericsum(*args)/len(args),
               'max':max(args),
               'min':min(args)
    }
    return results
In [106]:
find_stats(0.1,0.2,0.3,0.11,0.12,0.23)
Out[106]:
{'max': 0.3, 'mean': 0.17666666666666667, 'min': 0.1, 'sum': 1.06}
In [107]:
def timeseries(*values, **metadata):
    stats  = find_stats(*values)
    ploting_data = {}
    ploting_data['values'] = values
    ploting_data['stats'] = stats
    
    for key, value in metadata.items():
        ploting_data[key] = value
        
    return ploting_data
In [108]:
timeseries(0.01,0.001,0.011,0.013,0.089,
           group = "cancer",
           observations = "raw intensity",
           experiment = "gene expression",
           gene = "RC311")
Out[108]:
{'experiment': 'gene expression',
 'gene': 'RC311',
 'group': 'cancer',
 'observations': 'raw intensity',
 'stats': {'max': 0.089, 'mean': 0.0248, 'min': 0.001, 'sum': 0.124},
 'values': (0.01, 0.001, 0.011, 0.013, 0.089)}

Functions as arguments and return values

In [109]:
def func(x):
    return 2*x
In [110]:
func
Out[110]:
<function __main__.func>
In [111]:
print(func)
<function func at 0x7f0bc409b048>
In [112]:
type(func)
Out[112]:
function
In [113]:
twotimes = func
In [114]:
twotimes
Out[114]:
<function __main__.func>
In [115]:
twotimes(2)
Out[115]:
4
In [116]:
twotimes == func
Out[116]:
True
In [120]:
def sum_natural(n):
    s = 0
    for num in range(1,n+1):
        s += num
    return s

def square(x):
    return x*x

def sum_squares(n):
    s = 0 
    for num in range(1,n+1):
        s += square(num)
    return s

def cube(x):
    return x*x

def sum_cubes(n):
    s = 0 
    for num in range(1,n+1):
        s += cube(num)
    return s

def sum_func(n, func):
    s = 0 
    for num in range(1,n+1):
        s += func(num)
    return s
In [121]:
sum_squares(40)
Out[121]:
22140
In [123]:
sum_func(40, square)
Out[123]:
22140
In [124]:
sqr = lambda x:x*x
In [125]:
sum_func(40, lambda x:x*x)
Out[125]:
22140

8/1*3 + 8/5*7 + 8/9*11.... this series slowly converges pi

In [127]:
sum_func(10000, lambda n:8/((4*n-3)*(4*n-1)))
Out[127]:
3.1415426535898203
In [128]:
def make_adder(x):
    def adder(y):
        return x+y
    
    return adder
In [129]:
adder5 = make_adder(5)
In [130]:
adder5
Out[130]:
<function __main__.make_adder.<locals>.adder>
In [131]:
type(adder5)
Out[131]:
function
In [132]:
adder5(10)
Out[132]:
15
In [133]:
adder5(12)
Out[133]:
17
In [134]:
adder7 = make_adder(7)
In [135]:
adder7(5)
Out[135]:
12
In [136]:
def make_logger(prefix):
    def logger(*args):
        print(prefix, *args)
        
    return logger
In [137]:
info = make_logger("[INFO]:")
In [138]:
warning = make_logger("[WARNING]:")
In [139]:
info("Called function", square)
[INFO]: Called function <function square at 0x7f0bc404ed90>
In [140]:
warning("Something went wrong!")
[WARNING]: Something went wrong!
In [141]:
records = [
    ("A",40),
    ("B",86),
    ("C",48),
    ("D",75)
]
In [142]:
def get_score(record):
    return record[1]
In [143]:
max(records, key=get_score)
Out[143]:
('B', 86)
In [144]:
records = [
    ("A",90,6.6),
    ("B",100,6.7),
    ("C",110,7.0),
    ("D",95, 10.0)
] ## column0 is name, column1 is systolicBP, column2 is sugar in blood
In [145]:
def column(index):
    return lambda row:row[index]
In [149]:
max(records, key=column(1)) # person with highest BP
Out[149]:
('C', 110, 7.0)
In [150]:
max(records, key=column(2)) # person with highest sugar
Out[150]:
('D', 95, 10.0)
In [151]:
import os
In [152]:
words = ["one","two","three","four","five"]
In [154]:
max(words)
Out[154]:
'two'
In [155]:
max(words, key=len)
Out[155]:
'three'
In [156]:
files  = os.listdir(os.getcwd())
In [157]:
files
Out[157]:
['.ipynb_checkpoints',
 'push',
 'day1.ipynb',
 'links.txt',
 'day2.ipynb',
 'day2.html',
 'day3.ipynb',
 'data.csv',
 'day3.html',
 'Makefile',
 'day1.html']
In [158]:
max(files, key = os.path.getsize)
Out[158]:
'day1.html'
In [159]:
def compose(f, g):
    return lambda x: f(g(x))
In [161]:
twotimes_and_square = compose(square, twotimes)
In [163]:
twotimes_and_square(3)
Out[163]:
36

problem A number x is called fixed point of a function if x satisfies the equation f(x) = x. For some function f we locate fixed point by begining with an initial guess and applying f repeatedly, f(x), f(f(x)), f(f(f(x)))..write a python function to find fixed point of a function. it should take function as first argument, initial guess and accurracy (default= 0.0001) for equating f(x) = x

import math
fixed_point(math.cos, 1, 0.0001)
0.739054...
In [165]:
def fixed_point(f, guess, tollerence=0.0001):
    """
    computes fixed point of a mathematical function.
    x is fixed point of f if f(x) = x
    """
    prev = f(guess)
    current = f(prev)
    
    while abs(prev-current) > tollerence:
        prev, current = current, f(current)
        
    return current
    
In [166]:
import math
fixed_point(math.cos, 1)
Out[166]:
0.7390547907469174

bonus problem

`
a = 1934   
ma = 9431   rarrange the digits to form max number
mia = 1349  rearrange the digits to form min nmumber
a = ma - mia change a to difference of these two

make a function for above iteration. find fixed point of that function.

Decorators

In [167]:
def add(x,y):
    # print(x,y)
    return x+y
In [168]:
def sub(x,y):
    #print(x,y)
    return x-y

def mult(x,y):
    #print(x,y)
    return x*y
In [169]:
def debug(f):
    
    def wrapper(*args):
        print(f.__qualname__, args)
        return f(*args)
    
    return wrapper
    
In [170]:
addnew = debug(add)
In [171]:
addnew(3,4)
add (3, 4)
Out[171]:
7
In [172]:
add = debug(add)
In [173]:
add(5,6)
add (5, 6)
Out[173]:
11

sytactic sugar for this is

In [175]:
@debug  # equivalent to fib = debug(fib)
def fib(n):
    if n in [1,2]:
        return 1
    else:
        return fib(n-1) + fib(n-2)
In [176]:
fib(5)
fib (5,)
fib (4,)
fib (3,)
fib (2,)
fib (1,)
fib (2,)
fib (3,)
fib (2,)
fib (1,)
Out[176]:
5
In [177]:
@debug
def square(x):
    return x*x

@debug
def sumofsquares(x,y):
    return square(x) + square(y)
In [178]:
sumofsquares(3,4)
sumofsquares (3, 4)
square (3,)
square (4,)
Out[178]:
25
In [179]:
def debug(f):
    
    def wrapper(*args, **kwargs):
        print(f.__qualname__, args, kwargs)
        return f(*args, **kwargs)
    
    return wrapper
    
In [180]:
@debug
def add(x,y):
    return x+y
In [182]:
add(3,4, name="python")
add (3, 4) {'name': 'python'}
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-182-f5223b2b7fb4> in <module>()
----> 1 add(3,4, name="python")

<ipython-input-179-7a57c9a9d6af> in wrapper(*args, **kwargs)
      3     def wrapper(*args, **kwargs):
      4         print(f.__qualname__, args, kwargs)
----> 5         return f(*args, **kwargs)
      6 
      7     return wrapper

TypeError: add() got an unexpected keyword argument 'name'
In [183]:
volume = debug(cylinder_volume)
In [184]:
volume(radius=5, height=10)
cylinder_volume () {'radius': 5, 'height': 10}
Out[184]:
785.0
In [185]:
volume(5, 10)
cylinder_volume (5, 10) {}
Out[185]:
785.0
In [186]:
volume(5, height=10)
cylinder_volume (5,) {'height': 10}
Out[186]:
785.0

Whats the achievement?

  • Debugging code is at one place
  • It becomes easy to change it later
  • User of decorator need not worry about changes in decorator
In [193]:
%%file trace.py
import os

level = 0

def log(*args):
    if os.getenv("DEBUG") == "true":
        print(*args)

def trace(f):
    
    def g(*args):
        global level
        log("| " * level + "|--" + f.__qualname__, args)
        level += 1
        value = f(*args)
        level -= 1
        log("| " * level + "|--" + "return", value)
        return value
    
    return g
Overwriting trace.py
In [194]:
%%file sum.py
from trace import trace

@trace
def square(x):
    return x*x

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

if __name__ == "__main__":
    print(sum_of_squares(3,4))
Overwriting sum.py
In [195]:
!python sum.py
25
In [196]:
!DEBUG="true" python sum.py
|--sum_of_squares (3, 4)
| |--square (3,)
| |--return 9
| |--square (4,)
| |--return 16
|--return 25
25
In [199]:
%%file fib.py
import sys
from trace import trace

@trace
def fib(n):
    if n in [1,2]:
        return 1
    else:
        return fib(n-1) + fib(n-2)
    
def main():
    n = int(sys.argv[1])
    fib(n)
    
if __name__ == "__main__":
    main()
Overwriting fib.py
In [201]:
!DEBUG=true python fib.py 8
|--fib (8,)
| |--fib (7,)
| | |--fib (6,)
| | | |--fib (5,)
| | | | |--fib (4,)
| | | | | |--fib (3,)
| | | | | | |--fib (2,)
| | | | | | |--return 1
| | | | | | |--fib (1,)
| | | | | | |--return 1
| | | | | |--return 2
| | | | | |--fib (2,)
| | | | | |--return 1
| | | | |--return 3
| | | | |--fib (3,)
| | | | | |--fib (2,)
| | | | | |--return 1
| | | | | |--fib (1,)
| | | | | |--return 1
| | | | |--return 2
| | | |--return 5
| | | |--fib (4,)
| | | | |--fib (3,)
| | | | | |--fib (2,)
| | | | | |--return 1
| | | | | |--fib (1,)
| | | | | |--return 1
| | | | |--return 2
| | | | |--fib (2,)
| | | | |--return 1
| | | |--return 3
| | |--return 8
| | |--fib (5,)
| | | |--fib (4,)
| | | | |--fib (3,)
| | | | | |--fib (2,)
| | | | | |--return 1
| | | | | |--fib (1,)
| | | | | |--return 1
| | | | |--return 2
| | | | |--fib (2,)
| | | | |--return 1
| | | |--return 3
| | | |--fib (3,)
| | | | |--fib (2,)
| | | | |--return 1
| | | | |--fib (1,)
| | | | |--return 1
| | | |--return 2
| | |--return 5
| |--return 13
| |--fib (6,)
| | |--fib (5,)
| | | |--fib (4,)
| | | | |--fib (3,)
| | | | | |--fib (2,)
| | | | | |--return 1
| | | | | |--fib (1,)
| | | | | |--return 1
| | | | |--return 2
| | | | |--fib (2,)
| | | | |--return 1
| | | |--return 3
| | | |--fib (3,)
| | | | |--fib (2,)
| | | | |--return 1
| | | | |--fib (1,)
| | | | |--return 1
| | | |--return 2
| | |--return 5
| | |--fib (4,)
| | | |--fib (3,)
| | | | |--fib (2,)
| | | | |--return 1
| | | | |--fib (1,)
| | | | |--return 1
| | | |--return 2
| | | |--fib (2,)
| | | |--return 1
| | |--return 3
| |--return 8
|--return 21

problem: Write a decorator depricated that prints a warning message saying that function is deprecated everytime it is called

@depricated
def square(x):
    return x*x

print(square(4))
WARNING: function square is depricated.
16

problem: Write a decorator with_retries that continues to retry the function for 5 times if there is any exception raised while calling original function

from urllib.request import urlopen

@with_retries
def wget(url):
    return urlopen(url).read()

wget("http://google.com/nosuchpage/")
Failed to download, retrying...
Failed to download, retrying...
Failed to download, retrying...
Failed to download, retrying...
Failed to download, retrying...
Giving up!
In [202]:
def depricated(f):
    
    def wrapper(*args):
        print("WARNING: function", f.__qualname__, "is depricated.")
        return f(*args)
    return wrapper

@depricated
def square(x):
    return x*x

square(3)
WARNING: function square is depricated.
Out[202]:
9
In [203]:
square(5)
WARNING: function square is depricated.
Out[203]:
25
In [206]:
def with_retries(f):
    
    def wrapper(*args):
        for i in range(5):
            try:
                return f(*args)
            except Exception as e:
                print(e,"retrying..")
        print("Giving up!")
    return wrapper

from urllib.request import urlopen

@with_retries
def wget(url):
    return urlopen(url).read()
In [207]:
wget("http://google.com/nosuchpage")
HTTP Error 404: Not Found retrying..
HTTP Error 404: Not Found retrying..
HTTP Error 404: Not Found retrying..
HTTP Error 404: Not Found retrying..
HTTP Error 404: Not Found retrying..
Giving up!
In [209]:
%%file memoize.py

def memoize(func):
    cache = {}
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
        
    return wrapper
Overwriting memoize.py
In [211]:
from memoize import memoize
@memoize
def square(x):
    print("calling is square", x)
    return x*x
In [212]:
square(5)
calling is square 5
Out[212]:
25
In [213]:
square(5)
Out[213]:
25
In [216]:
%%file fib1.py
import sys
from memoize import memoize
from trace import trace

@memoize
@trace
def fib(n):
    if n in [1,2]:
        return 1
    else:
        return fib(n-1) + fib(n-2)
    
def main():
    n = int(sys.argv[1])
    fib(n)
    
if __name__ == "__main__":
    main()
Overwriting fib1.py
In [217]:
!DEBUG=true python fib1.py 8
|--fib (8,)
| |--fib (7,)
| | |--fib (6,)
| | | |--fib (5,)
| | | | |--fib (4,)
| | | | | |--fib (3,)
| | | | | | |--fib (2,)
| | | | | | |--return 1
| | | | | | |--fib (1,)
| | | | | | |--return 1
| | | | | |--return 2
| | | | |--return 3
| | | |--return 5
| | |--return 8
| |--return 13
|--return 21
In [218]:
!!DEBUG=true python fib.py 8
Out[218]:
['|--fib (8,)',
 '| |--fib (7,)',
 '| | |--fib (6,)',
 '| | | |--fib (5,)',
 '| | | | |--fib (4,)',
 '| | | | | |--fib (3,)',
 '| | | | | | |--fib (2,)',
 '| | | | | | |--return 1',
 '| | | | | | |--fib (1,)',
 '| | | | | | |--return 1',
 '| | | | | |--return 2',
 '| | | | | |--fib (2,)',
 '| | | | | |--return 1',
 '| | | | |--return 3',
 '| | | | |--fib (3,)',
 '| | | | | |--fib (2,)',
 '| | | | | |--return 1',
 '| | | | | |--fib (1,)',
 '| | | | | |--return 1',
 '| | | | |--return 2',
 '| | | |--return 5',
 '| | | |--fib (4,)',
 '| | | | |--fib (3,)',
 '| | | | | |--fib (2,)',
 '| | | | | |--return 1',
 '| | | | | |--fib (1,)',
 '| | | | | |--return 1',
 '| | | | |--return 2',
 '| | | | |--fib (2,)',
 '| | | | |--return 1',
 '| | | |--return 3',
 '| | |--return 8',
 '| | |--fib (5,)',
 '| | | |--fib (4,)',
 '| | | | |--fib (3,)',
 '| | | | | |--fib (2,)',
 '| | | | | |--return 1',
 '| | | | | |--fib (1,)',
 '| | | | | |--return 1',
 '| | | | |--return 2',
 '| | | | |--fib (2,)',
 '| | | | |--return 1',
 '| | | |--return 3',
 '| | | |--fib (3,)',
 '| | | | |--fib (2,)',
 '| | | | |--return 1',
 '| | | | |--fib (1,)',
 '| | | | |--return 1',
 '| | | |--return 2',
 '| | |--return 5',
 '| |--return 13',
 '| |--fib (6,)',
 '| | |--fib (5,)',
 '| | | |--fib (4,)',
 '| | | | |--fib (3,)',
 '| | | | | |--fib (2,)',
 '| | | | | |--return 1',
 '| | | | | |--fib (1,)',
 '| | | | | |--return 1',
 '| | | | |--return 2',
 '| | | | |--fib (2,)',
 '| | | | |--return 1',
 '| | | |--return 3',
 '| | | |--fib (3,)',
 '| | | | |--fib (2,)',
 '| | | | |--return 1',
 '| | | | |--fib (1,)',
 '| | | | |--return 1',
 '| | | |--return 2',
 '| | |--return 5',
 '| | |--fib (4,)',
 '| | | |--fib (3,)',
 '| | | | |--fib (2,)',
 '| | | | |--return 1',
 '| | | | |--fib (1,)',
 '| | | | |--return 1',
 '| | | |--return 2',
 '| | | |--fib (2,)',
 '| | | |--return 1',
 '| | |--return 3',
 '| |--return 8',
 '|--return 21']

problem Write a module cmdline.py to build command-line applications easily from your existing functions.

from cmdline import command, main

@command
def hello():
    """
    Prints hello
    """
    print("Hello World!")

@command
def cat(filename):
    """
    prints given file to standard ouput
    """
    print(open(filename.read())

@command   
def grep(word, filename):
    """
    greps given phrase in file
    """
    for line in open(filename):
        if word in line:
            print(line.strip())

if __name__ =="__main__":
    main()
$python commands.py hello
Hello World!
In [1]:
%%file cmdline.py

import sys

commands = {}

def command(f):
    commands[f.__name__] = f
    return f

def help_():
    print("Available commands")
    for cmd in command.keys():
        print(cmd.ljust(10), commands[cmd].__doc__.strip())
        
def main():
    cmdname = sys.argv[1]
    args = sys.argv[2:]
    if "help" in sys.argv:
        help_()
    elif cmdname in commands.keys():
        f = commands[cmdname]
        print("Executing command", cmdname)
        f(*args)
    else:
        print("No command", cmdname)
Overwriting cmdline.py
In [2]:
%%file commands.py
from cmdline import command, main

@command
def hello():
    """
    Prints hello
    """
    print("Hello World!")

@command
def cat(filename):
    """
    prints given file to standard ouput
    """
    print(open(filename).read())

@command   
def grep(word, filename):
    """
    greps given phrase in file
    """
    for line in open(filename):
        if word in line:
            print(line.strip())

if __name__ =="__main__":
    main()
Overwriting commands.py
In [3]:
!python commands.py grep "hello" commands.py
Executing command grep
def hello():
Prints hello
In [4]:
!python commands.py cat data.csv
Executing command cat
A1,B1,C1
A2,B2,C2
A3,B3,C3
A4,B4,C4
In [5]:
!python commands.py help
Available commands
Traceback (most recent call last):
  File "commands.py", line 27, in <module>
    main()
  File "/home/vikrant/trainings/2017/arcesium-oct-advpython/cmdline.py", line 19, in main
    help_()
  File "/home/vikrant/trainings/2017/arcesium-oct-advpython/cmdline.py", line 12, in help_
    for cmd in command.keys():
AttributeError: 'function' object has no attribute 'keys'

Decorators with arguments

In [12]:
%%file module.py
def backup(file, loc):
    # do some copying stuff
    #delete file
    print("calling backup")
    pass

print(__name__)
backup("hello.txt","/tmp/")
Overwriting module.py
In [13]:
!python module.py
__main__
calling backup
In [14]:
import module
module
calling backup
In [15]:
%%file module1.py
def backup(file, loc):
    # do some copying stuff
    #delete file
    print("calling backup")
    pass

print(__name__)
if __name__ == "__main__":
    backup("hello.txt","/tmp/")
Writing module1.py
In [16]:
!python module1.py
__main__
calling backup
In [17]:
import module1
module1
@with_retries(retries=3, delay=0.1)
def wget(url):


@debug(prefix="****")
def fib(n):


@login_required(role="admin)
def edit_interface():
In [21]:
import time
def with_retries(retries=5, delay=0):
    def decor(f):
        def wrapper(*args):
            print("retries=", retries, "delay=",delay)
            for i in range(retries):
                try:
                    return f(*args)
                except Exception as e:
                    print(f.__name__, args, "failed:", e)
                time.sleep(delay)
            print("Giving up!")
        return wrapper
    return decor
In [25]:
from urllib.request import urlopen

@with_retries(retries=3, delay=0.5)
def wget(url):
    """
    downloads given url from web
    """
    response = urlopen(url)
    if response:
        return response.read()
In [26]:
wget("http://google.com/nosuchpage/")
retries= 3 delay= 0.5
wget ('http://google.com/nosuchpage/',) failed: HTTP Error 404: Not Found
wget ('http://google.com/nosuchpage/',) failed: HTTP Error 404: Not Found
wget ('http://google.com/nosuchpage/',) failed: HTTP Error 404: Not Found
Giving up!
In [24]:
import time
from functools import partial

def with_retries(f=None, retries=5, delay=0):
    if f == None:
        return partial(with_retries, retries=retries, delay=delay)
    
    def g(*args):
        print("retries=", retries, "delay=",delay)
        for i in range(retries):
            try:
                return f(*args)
            except Exception as e:
                print(f.__name__, args, "failed:", e)
            time.sleep(delay)
        print("Giving up!")
    
    return g
In [27]:
print(wget)
<function with_retries.<locals>.g at 0x7f9c1c03da60>
In [28]:
wget.__name__
Out[28]:
'g'
In [29]:
from functools import wraps

def decorator(f):
    
    @wraps(f)
    def wrapper(*args):
        print("before calling function", f.__qualname__)
        result = f(*args)
        print("after calling function", f.__qualname__)
        return result
    return wrapper
In [30]:
@decorator
def add(x,y):
    """
    adds x and y
    """
    return x+y
In [31]:
add(2,3)
before calling function add
after calling function add
Out[31]:
5
In [32]:
print(add)
<function add at 0x7f9c1c033c80>
In [33]:
help(add)
Help on function add in module __main__:

add(x, y)
    adds x and y

In [34]:
help(wget)
Help on function g in module __main__:

g(*args)

In [35]:
add.__qualname__
Out[35]:
'add'

Working with databases

$ sqlite3 a.db
SQLite version 3.13.0 2016-05-18 10:57:30
Enter ".help" for usage hints.
sqlite> create table person(name varchar(100), email varchar(100));
sqlite> select * person 
   ...> ;
Error: near "person": syntax error
sqlite> select * from person;
sqlite> insert into person (name, email) values('alice','alice@example.com');
sqlite> select * from person;
alice|alice@example.com
sqlite>
In [36]:
import sqlite3
In [37]:
conn = sqlite3.connect("a.db")
In [38]:
cur = conn.cursor()
In [39]:
results = cur.execute("select * from person")
In [40]:
results
Out[40]:
<sqlite3.Cursor at 0x7f9c1c024f80>
In [41]:
results.fetchall()
Out[41]:
[('alice', 'alice@example.com')]
In [44]:
#bad way of doing it
def find_person(name):
    q = "select * from person where name='{}'".format(name)
    print(q)
    cur = conn.cursor()
    results = cur.execute(q)
    return results.fetchone()

find_person("alice")
select * from person where name='alice'
Out[44]:
('alice', 'alice@example.com')
In [45]:
def find_person(email):
    query = "select * from person where email=?"
    cur = conn.cursor()
    results = cur.execute(query, (email,))
    return results.fetchone()
In [46]:
find_person("alice@example.com")
Out[46]:
('alice', 'alice@example.com')
In [47]:
def query(conn, querystring, params=()):
    cur = conn.cursor()
    results = cur.execute(querystring, params)
    return results.fetchall()
In [48]:
query(conn, "select * from person where name=?", ("alice",))
Out[48]:
[('alice', 'alice@example.com')]
In [49]:
query(conn, "select * from person")
Out[49]:
[('alice', 'alice@example.com')]
In [50]:
conn.close()
In [51]:
conn = sqlite3.connect("a.db")
In [53]:
cur = conn.cursor()
persons = [("dilbert", "dilbert@dilbert.com"),
          ("calvin", "calvin@calvinhobes.com"),
          ("jerry", "jerry@disney.com")]
cur.executemany("insert into person values(?,?)", persons)
Out[53]:
<sqlite3.Cursor at 0x7f9c1c0353b0>
In [54]:
conn.commit() # this will save changes to db file
conn.close()
In [56]:
conn = sqlite3.connect("a.db")
cur = conn.cursor()
for name, email in  cur.execute("select * from person ORDER by name"):
    print(name, email)
alice alice@example.com
calvin calvin@calvinhobes.com
dilbert dilbert@dilbert.com
jerry jerry@disney.com

sqlalchemy

In [59]:
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey, create_engine

metadata = MetaData()
users = Table('users', metadata,
              Column('id', Integer, primary_key=True),
              Column("name", String),
              Column("email", String))
posts = Table("posts", metadata,
              Column('id', Integer, primary_key=True),
              Column("author_id", Integer, ForeignKey('users.id')),
              Column('title', String),
              Column("body", String))
In [60]:
engine = create_engine("sqlite:///test.db", echo=True)
metadata.create_all(engine)
2017-11-15 17:34:17,180 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
2017-11-15 17:34:17,184 INFO sqlalchemy.engine.base.Engine ()
2017-11-15 17:34:17,191 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
2017-11-15 17:34:17,201 INFO sqlalchemy.engine.base.Engine ()
2017-11-15 17:34:17,206 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("users")
2017-11-15 17:34:17,210 INFO sqlalchemy.engine.base.Engine ()
2017-11-15 17:34:17,215 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("posts")
2017-11-15 17:34:17,216 INFO sqlalchemy.engine.base.Engine ()
2017-11-15 17:34:17,219 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE users (
	id INTEGER NOT NULL, 
	name VARCHAR, 
	email VARCHAR, 
	PRIMARY KEY (id)
)


2017-11-15 17:34:17,221 INFO sqlalchemy.engine.base.Engine ()
2017-11-15 17:34:17,229 INFO sqlalchemy.engine.base.Engine COMMIT
2017-11-15 17:34:17,231 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE posts (
	id INTEGER NOT NULL, 
	author_id INTEGER, 
	title VARCHAR, 
	body VARCHAR, 
	PRIMARY KEY (id), 
	FOREIGN KEY(author_id) REFERENCES users (id)
)


2017-11-15 17:34:17,232 INFO sqlalchemy.engine.base.Engine ()
2017-11-15 17:34:17,241 INFO sqlalchemy.engine.base.Engine COMMIT
In [61]:
q = users.insert().values(name="Alice",email="alice@example.com")
metadata.bind = engine
q.execute()
2017-11-15 17:35:39,107 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, email) VALUES (?, ?)
2017-11-15 17:35:39,109 INFO sqlalchemy.engine.base.Engine ('Alice', 'alice@example.com')
2017-11-15 17:35:39,111 INFO sqlalchemy.engine.base.Engine COMMIT
Out[61]:
<sqlalchemy.engine.result.ResultProxy at 0x7f9c0677b320>
In [62]:
results = users.select().execute()
2017-11-15 17:36:02,090 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.email 
FROM users
2017-11-15 17:36:02,094 INFO sqlalchemy.engine.base.Engine ()
In [63]:
results.fetchall()
Out[63]:
[(1, 'Alice', 'alice@example.com')]
In [64]:
select = users.select()
In [65]:
q = select.where(users.columns.email=="alice@example.com")
In [66]:
print(q)
SELECT users.id, users.name, users.email 
FROM users 
WHERE users.email = ?
In [67]:
print(select)
SELECT users.id, users.name, users.email 
FROM users
In [68]:
select.where(users.columns.name=="Alice").execute().fetchall()
2017-11-15 17:41:07,164 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.email 
FROM users 
WHERE users.name = ?
2017-11-15 17:41:07,166 INFO sqlalchemy.engine.base.Engine ('Alice',)
Out[68]:
[(1, 'Alice', 'alice@example.com')]

How to work with existing db file?

In [70]:
engine = create_engine("sqlite:///test.db")
metadata = MetaData(engine)
In [72]:
metadata.tables
Out[72]:
immutabledict({})
In [75]:
metadata.reflect() # loads all the existing table definations
In [74]:
metadata.tables
Out[74]:
immutabledict({'posts': Table('posts', MetaData(bind=Engine(sqlite:///test.db)), Column('id', INTEGER(), table=<posts>, primary_key=True, nullable=False), Column('author_id', INTEGER(), ForeignKey('users.id'), table=<posts>), Column('title', VARCHAR(), table=<posts>), Column('body', VARCHAR(), table=<posts>), schema=None), 'users': Table('users', MetaData(bind=Engine(sqlite:///test.db)), Column('id', INTEGER(), table=<users>, primary_key=True, nullable=False), Column('name', VARCHAR(), table=<users>), Column('email', VARCHAR(), table=<users>), schema=None)})

Object-Relational Mapping(ORM)

In [76]:
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine("sqlite:///test.db")
Base = declarative_base()

# connect the engine
Base.metadata.bind = engine
In [89]:
class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String)
    
    def __repr__(self):
        return "User({0},{1})".format(self.name, self.email)

class Post(Base):
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True)
    author_id = Column(Integer, ForeignKey("users.id"))
    title = Column(String)
    body = Column(String)
    
    def __repr__(self):
        return "Post({author}, {title})".format(author = self.author_id, title=self.title) 
/home/vikrant/usr/local/anaconda3/lib/python3.6/site-packages/sqlalchemy/ext/declarative/clsregistry.py:120: SAWarning: This declarative base already contains a class with the same class name and module name as __main__.User, and will be replaced in the string-lookup table.
  item.__name__
---------------------------------------------------------------------------
InvalidRequestError                       Traceback (most recent call last)
<ipython-input-89-b2116186e2ce> in <module>()
----> 1 class User(Base):
      2     __tablename__ = "users"
      3     id = Column(Integer, primary_key=True)
      4     name = Column(String)
      5     email = Column(String)

/home/vikrant/usr/local/anaconda3/lib/python3.6/site-packages/sqlalchemy/ext/declarative/api.py in __init__(cls, classname, bases, dict_)
     62     def __init__(cls, classname, bases, dict_):
     63         if '_decl_class_registry' not in cls.__dict__:
---> 64             _as_declarative(cls, classname, cls.__dict__)
     65         type.__init__(cls, classname, bases, dict_)
     66 

/home/vikrant/usr/local/anaconda3/lib/python3.6/site-packages/sqlalchemy/ext/declarative/base.py in _as_declarative(cls, classname, dict_)
     86         return
     87 
---> 88     _MapperConfig.setup_mapping(cls, classname, dict_)
     89 
     90 

/home/vikrant/usr/local/anaconda3/lib/python3.6/site-packages/sqlalchemy/ext/declarative/base.py in setup_mapping(cls, cls_, classname, dict_)
    101         else:
    102             cfg_cls = _MapperConfig
--> 103         cfg_cls(cls_, classname, dict_)
    104 
    105     def __init__(self, cls_, classname, dict_):

/home/vikrant/usr/local/anaconda3/lib/python3.6/site-packages/sqlalchemy/ext/declarative/base.py in __init__(self, cls_, classname, dict_)
    129         self._extract_declared_columns()
    130 
--> 131         self._setup_table()
    132 
    133         self._setup_inheritance()

/home/vikrant/usr/local/anaconda3/lib/python3.6/site-packages/sqlalchemy/ext/declarative/base.py in _setup_table(self)
    393                     tablename, cls.metadata,
    394                     *(tuple(declared_columns) + tuple(args)),
--> 395                     **table_kw)
    396         else:
    397             table = cls.__table__

/home/vikrant/usr/local/anaconda3/lib/python3.6/site-packages/sqlalchemy/sql/schema.py in __new__(cls, *args, **kw)
    419                     "to redefine "
    420                     "options and columns on an "
--> 421                     "existing Table object." % key)
    422             table = metadata.tables[key]
    423             if extend_existing:

InvalidRequestError: Table 'users' is already defined for this MetaData instance.  Specify 'extend_existing=True' to redefine options and columns on an existing Table object.
In [78]:
from sqlalchemy.orm import sessionmaker
DBSession = sessionmaker(bind=engine)
session = DBSession()
In [79]:
new_user = User(name="anand", email="anand@xyz.com")
session.add(new_user)
session.commit()
In [80]:
q = session.query(User)
print(q.all())
[User(Alice,alice@example.com), User(anand,anand@xyz.com)]
In [81]:
q.filter(User.name=="anand").all()
Out[81]:
[User(anand,anand@xyz.com)]

Applying joins

In [82]:
q = session.query(User, Post)
In [83]:
print(q)
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, posts.id AS posts_id, posts.author_id AS posts_author_id, posts.title AS posts_title, posts.body AS posts_body 
FROM users, posts
In [84]:
print(q.filter(User.id==Post.author_id))
SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, posts.id AS posts_id, posts.author_id AS posts_author_id, posts.title AS posts_title, posts.body AS posts_body 
FROM users, posts 
WHERE users.id = posts.author_id
In [85]:
print(User.id == Post.author_id)
users.id = posts.author_id
In [87]:
q = session.query(Post)
q2 = q.join(User, User.id==Post.author_id)
In [88]:
print(q2)
SELECT posts.id AS posts_id, posts.author_id AS posts_author_id, posts.title AS posts_title, posts.body AS posts_body 
FROM posts JOIN users ON users.id = posts.author_id
In [ ]: