Aug 16-18, 2017
Anand Chitipothu & Vikrant Patil
These notes are available online at http://notes.pipal.in/2017/vmware-advpy
© Pipal Academy LLP
Let's look at the fib example again.
!python fib.py 5
!DEBUG=true python fib.py 5
!cat fib.py
Let us try to improve it.
%%file memoize.py
def memoize(f):
cache = {}
def g(*args):
if args not in cache:
cache[args] = f(*args)
return cache[args]
return g
%%file sq2.py
from memoize import memoize
@memoize
def square(x):
print("square", x)
return x*x
print(square(4))
print(square(4))
!python sq2.py
%%file fib2.py
import sys
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)
# fib = trace(fib)
# fib = memoize(fib)
def main():
n = int(sys.argv[1])
print(fib(n))
if __name__ == "__main__":
main()
!DEBUG=true python fib2.py 5
!time python fib.py 30
!time python fib2.py 30
# any typical decorator would look like this
def decorator(f):
print("defining function", f.__name__)
def g(*args):
print("before calling function", f.__name__)
result = f(*args)
print("after calling function", f.__name__)
return result
return g
Problem: Write a module cmdline.py to build command-line applications easily. Here is an example of how it can be used:
# hello.py
from cmdline import command, main
@command
def hello():
"""prints hello world message.
"""
print("hello world!")
@command
def goodbye():
"""prints good bye message.
"""
print("good bye!")
if __name__ == "__main__":
main()
The program should produce the following output when run.
$ python hello.py hello
hello world!
$ python hello.py goodbye
good bye!
Bonus Problem: Implement support for help in the cmdline.py module.
$ python hello.py help
Available commands:
hello - prints hello world message
goodbye - prints good bye message
help - prints this help message
Bonus Problem: Can you make these commands take arguments?
@command
def upper(name):
return name.upper()
And when called:
$ python hello.py upper python
PYTHON
$ python hello.py upper ten
TEN
%%file cmdline.py
"""Simple command-line framework.
"""
import sys
commands = {}
def command(f):
commands[f.__name__] = f
#print("defining command", f.__name__)
#print(commands)
return f
def main():
cmdname = sys.argv[1]
args = sys.argv[2:]
print("executing command", cmdname, args)
func = commands[cmdname]
func(*args)
%%file hello.py
from cmdline import command, main
#@command
def hello():
"""prints hello world message.
"""
print("hello world!")
hello = command(hello)
@command
def goodbye():
"""prints good bye message.
"""
print("good bye!")
@command
def whoareyou():
print("Python")
@command
def upper(name):
print(name.upper())
@command
def add(x, y):
print(int(x) + int(y))
print("---------")
if __name__ == "__main__":
main()
!python hello.py hello
!python hello.py upper python
!python hello.py add 3 4
from trace2 import trace
def square(x):
return x*x
@trace
def square2(x):
return x*x
We had some code like this:
@with_retries
def wget(url):
...
How many times to retry and the delay between retries is hardcoded in the with_retries implementation.
Wouldn't it be nice to specify that when using the decorator?
@with_retries(retries=3, delay=0.1)
def wget(url):
...
This is equivalant to:
decor = with_retries(retries=3, delay=0.1)
@decor
def wget(url):
...
And that is equivalant to:
decor = with_retries(retries=3, delay=0.1)
def wget(url):
...
wget = decor(wget)
Couple of practical use cases:
@app.route("/login")
def login():
...
@login_required(role="admin")
def admin_page():
...
import time
def with_retries(retries=5, delay=0):
def decor(f):
def g(*args):
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
return decor
Another way to write the samething in a slightly simpler way is:
import time
def with_retries(f=None, retries=5, delay=0):
if f is None:
# def decor(f):
# return with_retries(f=f, retries=retries, delay=delay)
# return decor
return lambda f: with_retries(f=f, retries=retries, delay=delay)
def g(*args):
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
How does iteration work in Python?
for x in [1, 2, 3, 4]:
print(x)
for c in "hello":
print(c)
for k in {"x": 1, "y": 2, "z": 3}:
print(k)
dictionary is an unordered collection, so the keys can appear in any order.
%%file 5.txt
one
two
three
four
five
for line in open("5.txt"):
print(repr(line))
x = iter(["a", "b", "c", "d"])
x
next(x)
next(x)
next(x)
next(x)
next(x)
# the largest word in the english dictionary
max(open("/usr/share/dict/words"), key=len)
What is an easy way to create an iterator?
def squares(numbers):
for n in numbers:
yield n*n
for x in squares([1, 2, 3, 4]):
print(x)
def squares(numbers):
print("BEGIN square", numbers)
for n in numbers:
print("computing square of", n)
yield n*n
print("END square")
sq = squares([1, 2, 3, 4])
sq
next(sq)
next(sq)
next(sq)
next(sq)
next(sq)
for x in squares([1, 2, 3, 4]):
print(x)
Q: How will Python know if a function is a generator or not?
If the function has any yield statement then the function becomes a generator function.
def f():
return 1
def g():
yield 1
f.__code__.co_flags
g.__code__.co_flags
import dis
dis.COMPILER_FLAG_NAMES
Q: Is it possible to have return inside a generator function?
def f():
for i in range(10000):
if i == 13:
return
yield i*i
Empty return statement is possible in Python 2 and Python 3.
Python 3.5+ supports return with a value, which is used for a special purpose called coroutines.
Problem: Write a generator countdown that takes a number n as argument and generates all numbers down to 0.
>>> for i in countdown(3):
... print(i)
3
2
1
0
Use while loop to implement this.
def countdown(n):
while n >= 0:
yield n
n -= 1
for i in countdown(3):
print(i)
[x*x for x in range(10)] # list comprehension
(x*x for x in range(10)) # generator expression
sum((x*x for x in range(1000000)))
When the generator expression is the only argument to a function, the parenthesis can be omited.
sum(x*x for x in range(1000000))
import os
def find(root):
"""Finds all files in the given directory tree.
"""
for path, dirnames, filenames in os.walk(root):
for f in filenames:
yield os.path.join(path, f)
def take(n, seq):
it = iter(seq)
return list(next(it) for i in range(n))
def integers():
i = 1
while True:
yield i
i += 1
def squares(numbers):
return (n*n for n in numbers)
take(10, squares(integers()))
def grep(pattern, seq):
return (x for x in seq if pattern in x)
files = find(".")
pyfiles = grep(".py", files)
print(take(10, pyfiles))
def count(seq):
i = 0
for x in seq:
i = i+1
return i
count(range(10))
def count(seq):
return sum(1 for x in seq)
count(range(10))
files = find(".")
pyfiles = grep(".py", files)
print(count(pyfiles))
def readlines(filenames):
"""Returns an iterators over lines in all the files specified.
"""
for f in filenames:
for line in open(f):
yield line
How many lines of Python code have we written in this course?
files = find(".")
pyfiles = grep(".py", files)
lines = readlines(pyfiles)
print(count(lines))
How many python functions have we written in this course?
files = find(".")
pyfiles = grep(".py", files)
lines = readlines(pyfiles)
functions = grep("def ", lines)
print(count(functions))
Problem: Write a function get_paragraphs to split given text into paragraphs.
The function should take a sequence of lines as argument and returns a sequence of paragraphs.
For sample input, see http://anandology.com/tmp/pg1342.txt
Once the function is there, we should be able to find:
def get_paragraphs(lines):
para = []
for line in lines:
if line.strip() != "":
para.append(line)
elif para:
yield "".join(para)
para = []
if para:
yield "".join(para)
lines = ["A1\n", "A2\n", "\n", "B1\n", "\n", "C1\n", "C2\n"]
get_paragraphs(lines)
list(get_paragraphs(lines))
For more info on generators, look at:
@media all and (max-width: 800px) {
.prompt {
display: none !important;
}
#header-container, #maintoolbar, #menubar {
display: none;
}
#notebook {
padding: 0px;
}
}
.training-header {
background: #ddd;
}