Aug 16-18, 2017
Anand Chitipothu & Vikrant Patil
These notes are available online at http://notes.pipal.in/2017/vmware-advpy
© Pipal Academy LLP
1 + 2
2 ** 1000
%%file hello.py
print("Hello, world!")
!python hello.py
1 + 2
x = 2
x = "hello"
x = 2
y = "3"
x + y
# this is comment
print(x*x)
Python has integers.
1 + 2
2 ** 1000
Python as floating point numbers.
1.2 + 3.4
Python has strings.
print("hello")
"hello"
Strings can be enclosed in single quotes or double quotes. Both mean exactly the same.
"hello" + 'world'
"Alice's book"
x = """This is a multi-line string
line two
line three
and the last line.
"""
x
print(x)
Python has lists.
x = ["a", "b", "c"]
len(x)
x[0]
x[:2] # first two elements of a list
How to get the last element?
x[-1]
"when in doubt, use brute force".split()[-1]
Python has another datatype called tuple to represent fixed-width records.
point = (3, 4)
x, y = point
x
y
person = ("Alice", "alice@example.com", 45, ["Admin", "Staff"])
person
Python has dictionaries for representing key-value pairs.
person = {
"name": "Alice",
"email": "alice@example.com",
"roles": ["Admin", "Staff"]
}
person["name"]
person["roles"]
Python also has a boolean type as well.
True
False
4 > 3
"hell" in "hello"
"yell" in "hello"
Python has a special type called None.
x = None
print(x)
Any function that doesn't return a value, returns None.
x = print("hello")
print(x)
def f():
pass
print(f())
A small example to demonstrate the elegance of Python.
%%file a.csv
A1,B1,C1
A2,B2,C2
A3,B3,C3
A4,B4,C4
open("a.csv").readlines()
# take every line
[line for line in open("a.csv")]
# take every line after stripping the newline char
[line.strip("\n") for line in open("a.csv")]
# split every line after stripping the newline char
[line.strip("\n").split(",") for line in open("a.csv")]
def square(x):
return x*x
square(4)
print(square(4))
print(square)
f = square
print(f)
f is square
Let us see why having functions as first class objects is important.
def square(x):
return x*x
def sum_of_squares(x, y):
return square(x) + square(y)
sum_of_squares(3, 4)
def cube(x):
return x*x*x
def sum_of_cubes(x, y):
return cube(x) + cube(y)
sum_of_cubes(3, 4)
def sumof(f, x, y):
return f(x) + f(y)
sumof(square, 3, 4)
sumof(cube, 3, 4)
sumof(len, "hello", "everyone")
Passing functions as arguments is so useful and common in Python that there are even some built-in functions that take functions as arguments.
words = ["one", "two", "three", "four", "five"]
max(words)
How about finding the longest word?
max(words, key=len)
def mylen(x):
print("mylen", x)
return len(x)
max(words, key=mylen)
Lets say we have records of students with name and marks.
records = [
("A", 67),
("B", 98),
("C", 56)
]
How to find the name of the person who got the maximum marks?
def get_marks(record):
return record[1]
max(records, key=get_marks)
max(records, key=get_marks)[0]
Q: How to sort in descending order?
min(records, key=get_marks)
sorted(records)
sorted(records, key=get_marks)
sorted(records, key=get_marks, reverse=True)
Problem: Write a function imax that takes a list of words and finds the maximum out of them ignoring the case.
>>> imax(["a", "B", "c", "D"])
'D'
>>> imax(["a", "B", "c"])
'c'
Hint:
"HelloWorld".lower()
"HelloWorld".upper()
def imax(words):
return max(words, key=ignore_case)
def ignore_case(s):
print("ignore_case", s)
return s.lower()
imax(["a", "B", "c"])
lambda expression¶square = lambda x: x*x
words = ["a", "B", "c", "D"]
max(words, key=lambda w: w.lower())
records = [
("A", 67),
("B", 98),
("C", 56)
]
max(records)
# find the record with maximum marks
max(records, key=lambda rec: rec[1])
Problem: Implement a function maximum that takes 2 values x and y and a key function as argument and finds the maximum of the values based on the key. Can you do this without using the built-in max function?
>>> maximum(3, -4, abs)
-4
>>> maximum("ten", "seven", len)
'seven'
>>> maximum("a", "B", lambda x: x.lower())
'B'
def diff(x, y):
return x-y
diff(5, 3)
diff(x=5, y=3)
diff(5, y=3)
diff(y=3, x=5)
def incr(x, amount=1):
return x+amount
incr(4, 2)
incr(4)
In python 3, you can enforce that some arguments can be passed only by name.
# python3 only
def incr(x, *, amount=1):
return x+amount
incr(4, amount=2)
print("hello")
print("hello", 1)
print("hello", 1, 2, 3, 4)
The print function can take any number of arguments.
How can we write a function that works like that?
def xprint(label, *args):
for a in args:
print(label, a)
xprint("[INFO]", 1, 2, 3, 4)
Problem: Write a function add that takes variable number of arguments and returns their sum.
>>> add(1, 2, 3)
6
>>> add(1, 2, 3, 4)
10
# Hint
sum([1, 2, 3, 4])
Problem: Write a function strjoin that takes a separator as first argument followed by variable number of string to join with that separator.
>>> strjoin("-", "a", "b", "c")
'a-b-c'
# Hint
"-".join(['a', 'b', 'c'])
We've seen how to handle variable number of positional arguments. How about taking variable number of keyword arguments?
def f(**kwargs):
print(kwargs)
f(x=1, y=2)
def render_tag(tagname, **attrs):
pairs = ['{}="{}"'.format(k, v) for k, v in attrs.items()]
pairs_str = " ".join(pairs)
return "<{} {}>".format(tagname, pairs_str)
print(render_tag("a", href="http://google.com"))
print(render_tag("input",
type="text",
name="email",
value="test@example.com"))
Now that we know how to pack variable number of positional arguments and variable number of keyword arguments, can you think of the reverse?
def f(a, b, c):
return a*b-c
args = [3, 2, 1]
Write a function call_func that takes a function and a list of arguments as arguments and calls that function with those arguments.
>>> call_func(square, [4])
16
>>> call_func(sum_of_squares, [3, 4])
25
>>> call_func(f, [3, 2, 1])
5
def call_func(f, args):
# unpack args when calling f
return f(*args)
call_func(square, [4])
call_func(f, [3, 2, 1])
The packing and unpacking are also used when doing multiple assignments.
line = "A,1,2,3,4,5,pass"
# python3 only
name, *marks, status = line.split(",")
name
marks
status
Just like we unpacked a list of arguments when calling a function, we can do the same for keyword arguments as well.
def diff(x, y):
return x-y
kwargs = {"x": 4, "y": 2}
How to call diff with kwargs?
diff(**kwargs) # equivalant of diff(x=4, y=2)
def incr(x, amount=1):
return x + amount
incr(5)
incr(5, 3)
Problem: Write a function centeralign that takes a string and optional width as arguments and center aligns that string in that width. If the width is not specified, it should take 10 as the default value.
>>> centeralign("abcd", 6)
' abcd '
>>> centeralign("abcd")
' abcd '
# Hint:
"hello".center(7)
Q: How does it show the single quotes?
Python has two ways to convert an object into a string. str and repr.
print(str("a"), repr("a"))
print(str(1), str("1"))
print(repr(1), repr("1"))
def append(value, result=[]):
result.append(value)
return result
x = [1, 2, 3]
print(append(4, x))
print(x)
y = [1, 2]
print(append(3, y))
z = append(1)
print(z)
z2 = append(2)
print(z2)
That is confusing, isn't it?
Let us try to understand what happens when default value is defined.
def peep(x):
print("peep", x)
return x
print("before defining incr")
def incr(x, amount=peep(1)):
return x+amount
print("after defining incr")
incr(4)
print("after call to incr(4)")
incr(5)
print("after call to incr(5)")
How to handle this kind issues? Simple! Make sure the default value is defined everytime the function is called.
def append(value, result=None):
if result is None:
# the result is initialized everytime the func is called
result = []
result.append(value)
return result
append(1)
append(2)
%%file q1.py
x = 1
y = x
x = 2
print(x, y)
!python q1.py
%%file q2.py
x = [1]
y = x
x = [2]
print(x, y)
!python q2.py
%%file q3.py
x = [1]
y = x
x.append(2)
print(x, y)
!python q3.py
def make_adder(x):
def adder(y):
return x + y
return adder
add5 = make_adder(5)
print(add5(3))
def make_adder(x):
print("make_adder", x)
def adder(y):
print("adder x={}, y={}".format(x, y))
return x + y
return adder
add5 = make_adder(5)
add5
add5(3)
def make_logger(prefix):
def logger(*args):
print(prefix, *args)
return logger
info = make_logger("[INFO]")
warn = make_logger("[WARN]")
info("Hello, world!")
warn("something went wrong.")
records = [
["A", 40],
["B", 86],
["C", 48],
["D", 75]
]
# return a function to get a column
def column(colindex):
def f(row):
return row[colindex]
return f
max(records, key=column(1))
Q: Can we give column name instead of column index?
Yes, if the data has column name.
records = [
{"name": "A", "marks": 40},
{"name": "B", "marks": 86},
{"name": "C", "marks": 48},
{"name": "D", "marks": 75},
]
def column(colname):
def f(row):
return row[colname]
return f
max(records, key=column('marks'))
%%file sum.py
def square(x):
print("square", x)
return x*x
def sum_of_squares(x, y):
print("sum_of_squares", x, y)
return square(x) + square(y)
if __name__ == "__main__":
print(sum_of_squares(3, 4))
!python sum.py
Can we get the prints without really adding prints manually inside each function?
%%file trace1.py
def trace(f):
def g(*args):
print(f.__name__, args)
value = f(*args)
print("return", value)
return value
return g
%%file sum1.py
from trace1 import trace
@trace
def square(x):
return x*x
#square = trace(square)
@trace
def sum_of_squares(x, y):
return square(x) + square(y)
#sum_of_squares = trace(sum_of_squares)
if __name__ == "__main__":
print(sum_of_squares(3, 4))
!python sum1.py
%%file trace2.py
import os
level = 0
def log(*args):
# print only if DEBUG is set to true
if os.getenv("DEBUG") == "true":
print(*args)
def trace(f):
def g(*args):
global level
log("| " * level + "|-- " + f.__name__, args)
level += 1
value = f(*args)
log("| " * level + "|-- " + "return", value)
level -= 1
return value
return g
%%file sum2.py
from trace2 import trace
@trace
def square(x):
return x*x
#square = trace(square)
@trace
def sum_of_squares(x, y):
return square(x) + square(y)
#sum_of_squares = trace(sum_of_squares)
if __name__ == "__main__":
print(sum_of_squares(3, 4))
!python sum2.py
%%file fib.py
import sys
from trace2 import trace
@trace
def fib(n):
if n == 0 or n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
def main():
n = int(sys.argv[1])
print(fib(n))
if __name__ == "__main__":
main()
!python fib.py 4
!DEBUG=true python fib.py 5
Problem: Write a function depricated that prints a warning message saying that the function is depricated everytime it is called.
@depricated
def square(x):
return x*x
print(square(4))
Should produce:
WARNING: function square is depricated.
16
Problem: Write a decorator function with_retries that continue to retry for 5 times if there is any exception raised in calling the original function.
# py3
from urllib.request import urlopen
# py2
#from urllib2 import urlopen
@with_retries
def wget(url):
return urlopen(url).read()
wget("http://google.com/no-such-page")
Should produce:
Failed to download, retrying...
Failed to download, retrying...
Failed to download, retrying...
Failed to download, retrying...
Failed to download, retrying...
Giving up!
from urllib.request import urlopen
def wget(url):
return urlopen(url).read()
try:
wget("http://google.com/no-such-page")
except Exception:
print("Got error!")
Let us look at the solution.
def with_retries(f):
def g(*args):
for i in range(5):
try:
return f(*args)
except Exception as e:
print(f.__name__, args, "failed:", e)
print("Giving up...")
return g
@with_retries
def foo(url):
raise Exception("Doom!")
foo("http://google.com/no-such-page")
%%file mymodule.py
print("BEGIN mymodule")
x = 1
def add(a, b):
return a+b
print("END mymodule")
!python mymodule.py
%%file a.py
print("begin")
import mymodule
print("after first import")
import mymodule
print("after second import")
print(mymodule.x)
print(mymodule.add(3, 4))
print("end")
!python a.py
Q: How to reload a module?
%%file b.py
import mymodule
# python3
import importlib
importlib.reload(mymodule)
# python2
# reload(mymodule)
!python b.py
__name__ magic variable¶%%file mymodule2.py
x = 1
def add(a, b):
return a+b
print(add(3, 4))
print(__name__)
!python mymodule2.py
!python -c "import mymodule2"
When the file is run as a script, the __name__ is set to "__main__", but when the file is imported as a module, it is set to the module name.
%%file mymodule3.py
x = 1
def add(a, b):
return a+b
if __name__ == "__main__":
# Run this block of code only when this file is executed
# as a script. Ignore this when imported as a module.
print(add(3, 4))
!python mymodule3.py
!python -c "import mymodule3"
%%file sq.py
"""The square module.
Long description of the module.
"""
def square(x):
"""Computes square of a number.
>>> square(4)
16
"""
return x*x
if __name__ == "__main__":
print(square(3))
!python sq.py
import sq
help(sq)