Sep 27-29, 2017 Vikrant Patil
These notes are available online at http://notes.pipal.in/2017/vmware-pune-advpy
© Pipal Academy LLP
4 + 2
4 ** 1000
5 / 2
5 // 2
"There" "are" "string"
"you" + "can " + "add" + "string"
"you can multiply by integers" * 3
"you can have ' inside a double quoted string"
'you can " inside single quoted string'
multi = """
this
is
multi line
string
"""
print(multi)
digits = list(range(10))
digits
digits[0]
digits[1:6] # new list containing items from 1st index till 6th (excluding)
digits[1:] # everything from 1st index
digits[:5] # strarting from zeroth till 5 th (excuding)
digits[-1]
digits[-2]
digits[:-1] # all items except last
digits[:]
digits[2:7:2]
word = "madam"
word[0]
word == word[::-1] # this is how I can check if given string is palindrome
You can do lot of stuff in sigle statement using list comprehensions
numbers = list(range(10))
numbers
[n*n for n in numbers]
[n*n*n for n in numbers]
[word.upper() for word in "Some sentence with some words in it".split() ]
[i for i in range(20) if i%2 == 0]
[dosomething(x) for x in items if somecondition(x)]
"it" in "not english"
"hell" in "hello"
person = {
"name":"Alice",
"email":"alice@wonder.land",
"roles":["Admin", "Staff"]
}
person['name']
[name for name in person.keys()]
[v for v in person.values()]
[(k,v) for k,v in person.items()]
%%file data.csv
A1,B1,C1
A2,B2,C2
A3,B3,C3
A4,B4,C5
open("data.csv").readlines()
[line.strip().split(",") for line in open("data.csv").readlines() ]
with open("data.csv") as f:
print([line.strip().split(",") for line in open("data.csv").readlines()])
If you need to open a file for writing, then you must close it. Only on closing file handle you can be sure that contents are flushed to disk. with block is one nice way to close file handles automativally
with open("sample.txt", "w") as sample:
sample.write("one\n")
sample.write("two\n")
sample.write("three\n")
Problem: creating a dictionary from two list
dict([(1,'a'), (2,'b'), (3, 'c')])
z = zip(range(5), ['0', '1', '2', '3', '4'])
list(z)
dict(zip(range(5), ['0', '1', '2', '3', '4']))
digits = range(4)
strdigits = [str(n) for n in digits]
list(digits)
strdigits
for d, c in zip(digits, strdigits):
print(d, c)
Positional arguments
def cylinder_volume(radius, height):
return 3.14*radius*radius*height
These are called positional arguments
cylinder_volume(5, 10) #radius first and then height
cylinder_volume(10, 5) # oops... it will wrong results and will be difficult to debug
How can we fix this?
cylinder_volume(radius=5, height=10)
cylinder_volume(5, height=10)
cylinder_volume(radius=5, 10)
Default arguments
import os
def location(name, home="/home/vikrant/"):
"""
returns virtualenv path on my machine
"""
return os.path.sep.join([home, "usr", "local", name])
location("jupyter")
location("jupyter", home= "/Users/vikrant")
Note that default values of arguments are defined at the time of function defination
import random
def number():
return random.random()
def func(a, b=number()):
print(a, b)
for i in range(5):
func(i)
def func(a, b=None):
if not b:
b = number()
print(a,b)
for i in [4,2,7,3,9,10]:
func(i)
You should also be very carefull when you pass default arguments that are mutable! basically always pass only immutable objects as default values.
def append(a, values=[]):
values.append(a)
return values
append("A")
append("B")
def append(a, values=None):
if values is None:
values = []
values.append(a)
return values
append("A")
append("B")
help(os.path.sep.join)
Only Named Arguments: With python3 there is sytanx to enforce few arguments with names only.
def sumation(values, *, initial=0):
total = initial
for v in values:
total += v
return total
sumation(range(10), initial=50)
sumation(range(10), 50)
sum([1,2,3,4,5])
If i want to build a genericsum function which works like this
genericsum(1,2)
3
genericsum(1,2,3,4,5)
genericsum(1.....)
Variable number of arguments : Ability to pass variable number of arguments to functions , makes the code scriptable
def func(*args):
pass
func(1, 2, 3)
func(1)
func("hello", "test")
def func(*args):
print(args)
func(1,2)
func(1,2,3,4,5)
def genericsum(*args):
return sum(list(args))
def genericsum(*args):
s = 0
for item in args:
s += item
return s
genericsum(1, 2)
genericsum(1,2,4,5,6)
def joinstrings(*args, sep=" "):
total = args[0]
for word in args[1:]:
total = total + sep + word
return total
joinstrings("Alanzo", "church", "thought", "about", "lambda", "calculas")
def joinstrings(*args, sep=" "):
return sep.join(args)
joinstrings("Alanzo", "church", "thought", "about", "lambda", "calculas")
joinstrings("hello", "world", sep=",")
joinstrings("hello", "world",",")
This is how you can pass variable number of named arguments
def make_parson(name, **kwargs):
person = {"name": name}
for key, value in kwargs.items():
person[key] = value
return person
p = make_parson(name="Haskell", surname="Curry", email="haskell@functional.expressions.com")
p['name']
p['email']
p
def f(a,v)
def f(a, v, *, name="x")
def f(*args)
def f(**kwargs)
genericsum(1, 2, 3, 4)
def find_stats(*args):
results = {'sum':genericsum(*args),
'mean':genericsum(*args)/len(args),
'max':max(args),
'min':min(args)
}
return results
find_stats(1,2, 3,4, 5, 6, 7, 7, 8)
def timeseries(*values, **metadata):
stats = find_stats(*values)
ploting_data = {}
ploting_data['value'] = values
ploting_data['stats'] = stats
for k, v in metadata.items():
ploting_data[k] = v
return ploting_data
timeseries(0, 0.01, 0.002, 0.0023, 0.0023,
group = "cancer",
observations = "raw intensity",
experiment = "gene expression",
gene = "RC311"
)
Functions in python are ordinary in a sense that they are not different from any other data type. But this makes functions very extraordinary as compared to other langauges
def func(x):
return x*x
func
a = zip([1, 2,3 ], [2, 3, 4])
a
type(a)
type(func)
square = func
square
func(2)
square(4)
square(5), func(4)
%%file module.py
def square(x):
return x*x
print(square(3))
!python module.py
import module
%%file module1.py
def square(x):
return x*x
print(square(3))
print(__name__)
!python module1.py
import module1
%%file module2.py
def square(x):
return x*x
def main():
print(square(3))
print(__name__)
if __name__ == "__main__": # __name__ is a special variable .. to identify if you are
# running a main script or doing just import
main()
!python module2.py
import module2
_
3 * 4
_ # this is special variable which stores results of last command executed
func
square
square == func
l = [1,2,3]
l2 = l
l is l2
l3 = [1, 2, 3]
l is l3
l == l3
def fone():
pass
def ftwo():
pass
fone == ftwo
just think about these functions
def sum_naturals(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**3
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
sum_squares(10)
sum_func(10, square)
lambda expression can be used to define annonymous functions on fly
f = lambda x: x*x
f(3)
sum_func(10, lambda x: x*x)
problem : The series 8/(1*3), 8/(5*3), 8/(9*11) .... slowly converges to pi
sum_func(1000, lambda n: 8/((4*n-3)*(4*n-1)))
def make_adder(x):
def adder(y):
return x+y
return adder
def f():
pass
f
def f(x):
return x+y
f(2)
def make_adder(y):
def adder(x):
return x+y
return adder
add5 = make_adder(5)
add5
add5(4)
add5(10)
add3 = make_adder(3)
add3(14)
def make_logger(prefix):
def logger(*args):
print(prefix, *args)
return logger
info = make_logger("[INFO]: ")
warn = make_logger("[WARN]: ")
warn("Something went wrong")
info("Called some function")
record =[
("A", 40),
("B", 86),
("C", 48),
("D", 75)
]
max([2,3,4,5])
max(record)
def get_marks(record):
return record[1]
get_marks(record[0])
max(record, key=get_marks)
max(["python", "java", "c++", "lisp", "smalltalk", "z++"])
max(["python", "java", "c++", "lisp", "smalltalk", "z++"], key=len)
max(record, key=lambda r:r[1])
records = [
("A",90, 6.6),
("B", 100, 6.7),
("C", 110, 7.0),
("D", 95, 10.0)
]
# column0 -> name
# column1 -> systolic BP
# column2 -> blood sugar
def column(index):
return lambda row:row[index]
max(records, key=column(1))
max(records, key=column(2))
problem:
compose which will take two functions f,g as arguments and return a function which will compute f(g(x))>>> f = lambda x: x*x
>>> g = lamdda x: x-1
>>> fg = compose(f, g)
>>> fg(3)
4
>>> gf = compose(g, f)
>>> gf(3)
8
def compose(f, g):
return lambda x: f(g(x))
f = lambda x: x*x
g = lambda x: x-1
fg = compose(f, g)
fg(3)
gf = compose(g, f)
gf(3)
words = "Lets print longest word in upper case from this statement".split()
words
def longest_word(words):
return max(words, key=len)
def uppercase(word):
return word.upper()
longest_upper = compose(uppercase, longest_word)
longest_upper(words)
Problem: Write a function zip_with which will take function f as argument and return a function which zips two lists into single list by combining element by element with f
problem: Write a generic polynomial generator, which takes coefficients as aregument and returns a function which calculates polynomial function for given argument.
P2 = make_polynomial(1,1,1) # P(x) = x^2 + x + 1
P2(2) #compute P(x) for for x = 2
7
fixed_point(math.cos, 1, 0.0001)
0.7390547907469174
def zip_with(f):
def func(list1, list2):
return [f(x,y) for x,y in zip(list1, list2)]
return func
for x,y in zip([1,2,3], ['a', 'b', 'c']):
print(x,y)
def add(x, y):
return x+y
vector_adder = zip_with(add)
vector_adder([1,2,3,4], [5,6,7,8])
list_multiplier = zip_with(lambda x, y: x*y)
list_multiplier([1,2,3,4], [5,6,7,8])
string_con = zip_with(add)
string_con(["Hello", "Goodbye"], ["python", "C++"])
def usual_way_polynomial(coeffs, x):
"""
computes value of polynomial given coefficients and x
>>> usual_way_polynomial([1,1,1], 2) # computes x^2 + x + 1
7
"""
s = 0
for i, c in enumerate(reversed(coeffs)):
s += c*x**i
return s
def make_polynomial(*coeffs):
"""
make a polynomial as function given coefficients
>>> P2 = make_polynomial([1,1,1])
>>> P2(2)
7
"""
def poly(x):
s = 0
for i, c in enumerate(reversed(coeffs)):
s += c*x**i
return s
return poly
#return lambda x: sum([c*x**i for c,i in enumerate(reversed(coeffs))])
usual_way_polynomial([1,1,1], 2)
P2 = make_polynomial(1, 2, 1)
P2(3)
def fixed_point(f, guess, tollerance=0.00001):
"""
computes fixed point of a function
"""
prev = f(guess)
current = f(prev)
while abs(prev-current) >tollerance:
prev, current = current, f(current)
return current
import math
fixed_point(math.cos, 1)
math.cos(0.7390822985224024)
%%file sq1.py
def square(x):
return x*x
def test():
print(square(3))
if __name__ == "__main__":
test()
!python sq1.py
%%file sq2.py
def square(x):
return x*x
def test():
if square(3) == 9:
print("Passed")
if __name__ == "__main__":
test()
!python sq2.py
%%file sq3.py
def square(x):
return x*x
def test():
assert square(3) == 9
assert square(0) == 0
assert square(-3) == 9
assert square(-1) == 0
if __name__ == "__main__":
test()
!python sq3.py
py.test is a third party library which makes testing very easy. to install it
pip3 install pytest
!pytest sq3.py
%%file weekday.py
import datetime
def now():
return datetime.datetime.now()
def weekday():
t = now()
return t.strftime("%A")
if __name__ == "__main__":
print(weekday())
!python weekday.py
%%file test_weekday.py
import weekday
import datetime
def test_weekday(monkeypatch):
faketime = 2010, 1, 1
def fakenow():
return datetime.datetime(*faketime)
monkeypatch.setattr(weekday, "now", fakenow)
faketime = 2010, 1, 1
assert weekday.weekday() == "Friday"
faketime = 2010, 1, 2
assert weekday.weekday() == "Saturday"
!pytest test_weekday.py
import datetime
datetime.datetime(2010, 1, 1)
faketime = 2010, 1, 1
faketime
datetime.datetime(*faketime)
Whats the easiest and surest way of debuging?
def add(x, y):
print("add ", x, y)
return x+y
dir(add)
def sub(x, y):
return x-y
def mult(x, y):
return x*y
def fib(n):
if n in [1, 2]:
return 1
else:
return fib(n-1) + fib(n-2)
def debug(f):
def wrapper(*args):
print(f.__qualname__, args)
return f(*args)
return wrapper
sub(3,4)
subnew = debug(sub)
subnew(3,4)
sub = debug(sub)
sub(4,5)
fib = debug(fib)
fib(5)
There is syntactic sugar for this!
@debug
def fib(n):
if n in [1, 2]:
return 1
else:
return fib(n-1)+ fib(n-2)
Lets do some advanced debuging
%%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
%%file sum.py
from trace import trace
@trace
def square(x):
return x*x
#square = trace(square)
@trace
def sum_of_squares(x, y):
return square(x) + square(y)
if __name__ == "__main__":
sum_of_squares(3, 4)
!DEBUG=true python sum.py
!python sum.py
%%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()
!DEBUG=true python fib.py 6
Problem : Write a function depricated that prints a warning message thet the function is depricated everytime it is called
@depricated
def square(x):
return x*x
print(square(4)
WARNING: function square is depricated
16
Problem: Write a decorator function with_retries that continues to retry 5 times if there is any exception raised in original function.
from urllib.request import urlopen
@with_retries
def wget(url):
return urlopen(url).read()
wget("http://google.com/nourl")
Failed to download, retrying..
Failed to download, retrying..
Failed to download, retrying..
Failed to download, retrying..
Failed to download, retrying..
Giving up!
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(4)
def f():
print("f")
x = f()
x
print(x)
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()
wget("http://lksdfdsf.sdfsfs.in")
Lets look closely at fib!
!DEBUG=true python fib.py 5
Can we improve this?
%%file memoize.py
def memoize(func):
cache = {}
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
import imp
imp.reload(memoize)
from memoize import memoize
@memoize
def square(x):
print("square", x)
return x*x
square(2)
square(4)
square(2)
%%file fib1.py
import sys
from trace import trace
from memoize import memoize
@memoize
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()
!time -p python fib.py 30
!time -p python fib1.py 30
#f = decorator2(decorator1(f))
%%file fib2.py
import sys
from memoize import memoize
from trace import trace
@memoize # fib = memoize(trace(fib)) ^
@trace # fib = trace(fib) |
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()
!DEBUG=true python fib2.py 10 # with memoize
!DEBUG=true python fib.py 5