0% found this document useful (0 votes)
206 views

Python Practice Book 1.0

Uploaded by

sandro car
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
206 views

Python Practice Book 1.0

Uploaded by

sandro car
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 104

! » Python Prac!

ce Book

Python Practice Book


Welcome to Python Prac!ce Book.

About this Book

This book is prepared from the training notes of Anand Chi!pothu.

Anand conducts Python training classes on a semi-regular basis in


Bangalore, India. Checkout out the upcoming trainings if you are
interested.

Table of Contents

1. Ge"ng Started

1.1. Running Python Interpreter


1.2. Running Python Scripts
1.3. Datatypes
1.4. Variables
1.5. Func!ons
1.6. Wri!ng Custom Func!ons
1.7. Condi!onal Expressions
1.8. Lists
1.9. Modules

2. Working with Data

2.1. Lists
2.2. Tuples
2.3. Sets
2.4. Strings
2.5. Working With Files
2.6. List Comprehensions
2.7. Dic!onaries

3. Modules

3.1. Standard Library


3.2. Installing third-party modules

4. Object Oriented Programming

4.1. State
4.2. Classes and Objects
4.3. Inheritance
4.4. Special Class Methods
4.5. Errors and Excep!ons

5. Iterators & Generators

5.1. Iterators
5.2. Generators
5.3. Generator Expressions
5.4. Itertools

6. Func!onal Programming

6.1. Recursion
6.2. Higher Order Func!ons & Decorators
6.3. exec & eval

License

Python Prac!ce Book by Anand Chi!pothu is licensed under a


Crea!ve Commons A#ribu!on-NonCommercial 4.0 Interna!onal
License.
! » 1. Ge!ng Started

1. Getting Started

1.1. Running Python Interpreter

Python comes with an interac"ve interpreter. When you type python

in your shell or command prompt, the python interpreter becomes


ac"ve with a >>> prompt and waits for your commands.

$ python
Python 3.7.4 (v3.7.4:e09359112e, Jul 8 2019, 14:54:52)
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more
information.
>>>

Now you can type any valid python expression at the prompt. python
reads the typed expression, evaluates it and prints the result.

>>> 42
42
>>> 4 + 2
6

Problem 1: Open a new Python interpreter and use it to find the value
of 2 + 3 .

1.2. Running Python Scripts

Open your text editor, type the following text and save it as hello.py .
print("hello, world!")

And run this program by calling python hello.py . Make sure you
change to the directory where you saved the file before doing it.

$ python hello.py
hello, world!

1.3. Datatypes

Python has support for all basic datatypes and also have very powerful
compound datatypes.

Python has integers.

>>> 1 + 2
3

Python is pre#y good at handling very large numbers as well. For


example, let us try to compute 2 raises to the power of 1000.

>>> 2 ** 1000
107150860718626732094842504906000181056140481170553360744375038837035105112493

That is a pre#y big numbers, isn’t it? Can you count how many digits it
has?

Python has floa"ng point numbers.

>>> 1.2 + 2.3


3.5
Python has strings.

>>> "hello world"


'hello world'
>>> print("hello world")
hello world

String can be enclosed either in single quotes or double quotes. Both


are exactly the same. In Python, strings are very versa"le and it very
easy to work with them.

>>> 'hello' + 'world'


'helloworld'

>>> "hello" * 3
'hellohellohello'

>>> print("=" * 40)


========================================

The built-in func"on len is used to find the length of a string.

>>> len('helloworld')
10

Python supports mul"-line strings too. They are enclosed in three


double quotes or three single quotes.

text = """This is a multi-line string.


Line 2
Line 3
and the text may have "quotes" too.
"""
>>> print(text)
This is a multi-line string.
Line 2
Line 3
and the text may have "quotes" too.

Python supports the usual escape codes. \n indicates new line, \t

indicates a tab etc.

>>> print("a\nb\nc")
a
b
c

Python has lists. Lists are one of the most useful data types Python.

>>> x = ["a", "b", "c"]


>>> x
['a', 'b', 'c']
>>> len(x)
3
>>> x[1]
'b'

Python has another datatype called tuple for represen"ng fixed width
records. Tuples behave just like lists, but they are immutable.

>>> point = (2, 3)


>>> point
(2, 3)

When wri"ng tuples, the parenthesis can be omi#ed most of the


"mes.
>>> point = 2, 3
>>> point
(2, 3)

It is also possible to assign a tuple mul"ple values at once:

>>> yellow = (255, 255, 0)


>>> r, g, b = yellow
>>> print(r, g, b)
255 255 0

Python has a dictionary datatype for represen"ng name-value pairs.

>>> person = {"name": "Alice", "email": "[email protected]"}


>>> person['name']
'Alice'
>>> person['email']
'[email protected]'

Python has a set datatype too. A set is an unordered collec"on of


elements.

>>> x = {1, 2, 3, 2, 1}
>>> x
{1, 2, 3}

Python has a boolean type. It has two special values True and
False to represent truth and false.

Finally, Python has a special type called None to represent nothing.

>>> x = None
>>> print(x)
None
Now you know most of the common data structures of Python. While
they look very simple, mastering them takes a bit of prac"ce. Make
sure you go through all the examples and the prac"ce problems in the
subsequent sec"ons.

1.4. Variables

You’ve already seen variables in the previous sec"on. Let us look at


them closely now.

In Python, variables don’t have a type. They are just placeholders


which can hold any type of values.

>>> x = 5
>>> x
5
>>> x = 'hello'
>>> x
'hello'

It is important to no"ce the difference between variables and strings.


O%en new programmers get tricked by this. Can you spot any error in
the following example?

name = “Alice” print(“name”)

1.5. Functions

Python has many built-in func"ons. The print is the most commonly
used built-in func"on.

>>> print('hello')
hello
>>> print('hello', 1, 2, 3)
hello 1 2 3
We’ve also see the len func"on in the previous sec"ons. The len

func"on computes the length of a string, list or other collec"ons.

>>> len("hello")
5
>>> len(['a', 'b', 'c'])
3

One important thing about Python is that it doesn’t allow opera"ons


on incompa"ble data types. For example:

>>> 5 + "2"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

That is because it is not possible to add a number to a string. We need


to either convert 5 into a string or "2"` into a number. The built-in

function ``int converts a string into a number and the str func"on
converts any value into a string.

>>> int("5")
5
>>> str(5)
'5'

>>> 5 + int("2")
7
>>> str(5) + "2"
'52'

1.5.1. Example: Counting the number of digits in a


number
Let us write a program to compute number of digits in a number. Let
us look at some numbers first.

>>> 12345
12345

>>> 2 ** 100
1267650600228229401496703205376

>>> 2 ** 1000
107150860718626732094842504906000181056140481170553360744375038837035105112493

We can combile the previously men"oned built-in func"ons to solve


this.

>>> len(str(12345))
5
>>> len(str(2 ** 100))
31
>>> len(str(2 * 1000))
302

1.6. Writing Custom Functions

Just like a value can be associated with a name, a piece of logic can
also be associated with a name by defining a func"on.

>>> def square(x):


... return x * x
...
>>> square(5)
25
The body of the func"on is indented. Indenta"on is the Python’s way
of grouping statements.

The ... is the secondary prompt, which the Python interpreter uses
to denote that it is expec"ng some more input.

The func"ons can be used in any expressions.

>>> square(2) + square(3)


13
>>> square(square(3))
81

Exis"ng func"ons can be used in crea"ng new func"ons.

>>> def sum_of_squares(x, y):


... return square(x) + square(y)
...
>>> sum_of_squares(2, 3)
13

Func"ons are just like other values, they can assigned, passed as
arguments to other func"ons etc.

>>> f = square
>>> f(4)
16

>>> def fxy(f, x, y):


... return f(x) + f(y)
...
>>> fxy(square, 2, 3)
13

It is important to understand, the scope of the variables used in


func"ons.
Lets look at an example.

x = 0
y = 0
def incr(x):
y = x + 1
return y
incr(5)
print(x, y)

Variables assigned in a func"on, including the arguments are called the


local variables to the func"on. The variables defined in the top-level
are called global variables.

Changing the values of x and y inside the func"on incr won’t


effect the values of global x and y .

But, we can use the values of the global variables.

pi = 3.14
def area(r):
return pi * r * r

When Python sees use of a variable not defined locally, it tries to find
a global variable with that name.

However, you have to explicitly declare a variable as global to modify


it.

numcalls = 0
def square(x):
global numcalls
numcalls = numcalls + 1
return x * x
Problem 2: How many mul"plica"ons are performed when each of the
following lines of code is executed?

print(square(5))
print(square(2*5))

Problem 3: What will be the output of the following program?

x = 1
def f():
return x
print(x)
print(f())

Problem 4: What will be the output of the following program?

x = 1
def f():
x = 2
return x
print(x)
print(f())
print(x)

Problem 5: What will be the output of the following program?

x = 1
def f():
y = x
x = 2
return x + y
print(x)
print(f())
print(x)

Problem 6: What will be the output of the following program?


x = 2
def f(a):
x = a * a
return x
y = f(3)
print(x, y)

Func"ons can be called with keyword arguments.

>>> def difference(x, y):


... return x - y
...
>>> difference(5, 2)
3
>>> difference(x=5, y=2)
3
>>> difference(5, y=2)
3
>>> difference(y=2, x=5)
3

And some arguments can have default values.

>>> def increment(x, amount=1):


... return x + amount
...
>>> increment(10)
11
>>> increment(10, 5)
15
>>> increment(10, amount=2)
12

There is another way of crea"ng func"ons, using the lambda operator.

>>> cube = lambda x: x ** 3


>>> fxy(cube, 2, 3)
35
>>> fxy(lambda x: x ** 3, 2, 3)
35
No"ce that unlike func"on defina"on, lambda doesn’t need a return .
The body of the lambda is a single expression.

The lambda operator becomes handy when wri"ng small func"ons to


be passed as arguments etc. We’ll see more of it as we get into solving
more serious problems.

Python provides some useful built-in func"ons.

>>> min(2, 3)
2
>>> max(3, 4)
4

The built-in func"on len computes length of a string.

>>> len("helloworld")
10

The built-in func"on int converts string to ingeter and built-in


func"on str converts integers and other type of objects to strings.

>>> int("50")
50
>>> str(123)
"123"

Problem 7: Write a func"on count_digits to find number of digits in


the given number.

>>> count_digits(5)
1
>>> count_digits(12345)
5
Methods are special kind of func"ons that work on an object.

For example, upper is a method available on string objects.

>>> x = "hello"
>>> print(x.upper())
HELLO

As already men"oned, methods are also func"ons. They can be


assigned to other variables can be called separately.

>>> f = x.upper
>>> f()
'HELLO'

Problem 8: Write a func"on istrcmp to compare two strings, ignoring


the case.

>>> istrcmp('python', 'Python')


True
>>> istrcmp('LaTeX', 'Latex')
True
>>> istrcmp('a', 'b')
False

1.7. Conditional Expressions

Python provides various operators for comparing values. The result of


a comparison is a boolean value, either True or False .

>>> 2 < 3
True
>>> 2 > 3
False
Here is the list of available condi"onal operators.

== equal to
!= not equal to
< less than
> greater than
<= less than or equal to
>= greater than or equal to

It is even possible to combine these operators.

>>> x = 5
>>> 2 < x < 10
True
>>> 2 < 3 < 4 < 5 < 6
True

The condi"onal operators work even on strings - the ordering being


the lexical order.

>>> "python" > "perl"


True
>>> "python" > "java"
True

There are few logical operators to combine boolean values.

a and b is True only if both a and b are True.


a or b is True if either a or b is True.
not a is True only if a is False.
>>> True and True
True
>>> True and False
False
>>> 2 < 3 and 5 < 4
False
>>> 2 < 3 or 5 < 4
True

Problem 9: What will be output of the following program?

print(2 < 3 and 3 > 1)


print(2 < 3 or 3 > 1)
print(2 < 3 or not 3 > 1)
print(2 < 3 and not 3 > 1)

Problem 10: What will be output of the following program?

x = 4
y = 5
p = x < y or x < z
print(p)

The if statement is used to execute a piece of code only when a


boolean expression is true.

>>> x = 42
>>> if x % 2 == 0: print('even')
even
>>>

In this example, print('even') is executed only when x % 2 == 0 is


True .
The code associated with if can be wri#en as a separate indented
block of code, which is o%en the case when there is more than one
statement to be executed.

>>> if x % 2 == 0:
... print('even')
...
even
>>>

The if statement can have op"onal else clause, which is executed


when the boolean expression is False .

>>> x = 3
>>> if x % 2 == 0:
... print('even')
... else:
... print('odd')
...
odd
>>>

The if statement can have op"onal elif clauses when there are
more condi"ons to be checked. The elif keyword is short for else

if , and is useful to avoid excessive indenta"on.

>>> x = 42
>>> if x < 10:
... print('one digit number')
... elif x < 100:
... print('two digit number')
... else:
... print('big number')
...
two digit number
>>>
Problem 11: What happens when the following code is executed? Will
it give any error? Explain the reasons.

x = 2
if x == 2:
print(x)
else:
print(y)

Problem 12: What happens the following code is executed? Will it give
any error? Explain the reasons.

x = 2
if x == 2:
print(x)
else:
x +

1.8. Lists

Lists are one of the great datastructures in Python. We are going to


learn a li#le bit about lists now. Basic knowledge of lists is requrired to
be able to solve some problems that we want to solve in this chapter.

Here is a list of numbers.

>>> x = [1, 2, 3]

And here is a list of strings.

>>> x = ["hello", "world"]

List can be heterogeneous. Here is a list containings integers, strings


and another list.
>>> x = [1, 2, "hello", "world", ["another", "list"]]

The built-in func"on len works for lists as well.

>>> x = [1, 2, 3]
>>> len(x)
3

The [] operator is used to access individual elements of a list.

>>> x = [1, 2, 3]
>>> x[1]
2
>>> x[1] = 4
>>> x[1]
4

The first element is indexed with 0 , second with 1 and so on.

We’ll learn more about lists in the next chapter.

1.9. Modules

Modules are libraries in Python. Python ships with many standard


library modules.

A module can be imported using the import statement.

Lets look at time module for example:

>>> import time


>>> time.asctime()
'Tue Sep 11 21:42:06 2012'
The asctime func"on from the time module returns the current "me
of the system as a string.

The sys module provides access to the list of arguments passed to


the program, among the other things.

The sys.argv variable contains the list of arguments passed to the


program. As a conven"on, the first element of that list is the name of
the program.

Lets look at the following program echo.py that prints the first
argument passed to it.

import sys
print(sys.argv[1])

Lets try running it.

$ python echo.py hello


hello
$ python echo.py hello world
hello

There are many more interes"ng modules in the standard library. We’ll
learn more about them in the coming chapters.

Problem 13: Write a program add.py that takes 2 numbers as


command line arguments and prints its sum.

$ python add.py 3 5
8
$ python add.py 2 9
11
! » 2. Working with Data

2. Working with Data

2.1. Lists

We’ve already seen quick introduc!on to lists in the previous chapter.

>>> [1, 2, 3, 4]
[1, 2, 3, 4]
>>> ["hello", "world"]
["hello", "world"]
>>> [0, 1.5, "hello"]
[0, 1.5, "hello"]
>>> [0, 1.5, "hello"]
[0, 1.5, "hello"]

A List can contain another list as member.

>>> a = [1, 2]
>>> b = [1.5, 2, a]
>>> b
[1.5, 2, [1, 2]]

The built-in func!on range can be used to create a sequence of


conseque!ve integers.

The range func!on returns a specical range object that behaves like a
list. To get a real list from it, you can use the list func!on.
>>> x = range(1, 4)
>>> x
range(1, 4)
>>> x[0]
1
>>> len(x)
3

The built-in func!on len can be used to find the length of a list.

>>> a = [1, 2, 3, 4]
>>> len(a)
4

The + and * operators work even on lists.

>>> a = [1, 2, 3]
>>> b = [4, 5]
>>> a + b
[1, 2, 3, 4, 5]
>>> b * 3
[4, 5, 4, 5, 4, 5]

List can be indexed to get individual entries. Value of index can go


from 0 to (length of list - 1).

>>> x = [1, 2]
>>> x[0]
1
>>> x[1]
2

When a wrong index is used, python gives an error.


>>> x = [1, 2, 3, 4]
>>> x[6]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
IndexError: list index out of range

Nega!ve indices can be used to index the list from right.

>>> x = [1, 2, 3, 4]
>>> x[-1]
4
>>> x [-2]
3

We can use list slicing to get part of a list.

>>> x = [1, 2, 3, 4]
>>> x[0:2]
[1, 2]
>>> x[1:4]
[2, 3, 4]

Even nega!ve indices can be used in slicing. For example, the


following examples strips the last element from the list.

>>> x[0:-1]
[1, 2, 3]

Slice indices have useful defaults; an omi"ed first index defaults to


zero, an omi"ed second index defaults to the size of the list being
sliced.
>>> x = [1, 2, 3, 4]
>>> a[:2]
[1, 2]
>>> a[2:]
[3, 4]
>>> a[:]
[1, 2, 3, 4]

An op!onal third index can be used to specify the increment, which


defaults to 1.

>>> x = range(10)
>>> x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x[0:6:2]
[0, 2, 4]

We can reverse a list, just by providing -1 for increment.

>>> x[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

List members can be modified by assignment.

>>> x = [1, 2, 3, 4]
>>> x[1] = 5
>>> x
[1, 5, 3, 4]

Presence of a key in a list can be tested using in operator.

>>> x = [1, 2, 3, 4]
>>> 2 in x
True
>>> 10 in x
False
Values can be appended to a list by calling append method on list. A
method is just like a func!on, but it is associated with an object and
can access that object when it is called. We will learn more about
methods when we study classes.

>>> a = [1, 2]
>>> a.append(3)
>>> a
[1, 2, 3]

Problem 1: What will be the output of the following program?

x = [0, 1, [2]]
x[2][0] = 3
print(x)
x[2].append(4)
print(x)
x[2] = 2
print(x)

2.1.1. The for Statement

Python provides for statement to iterate over a list. A for

statement executes the specified block of code for every element in a


list.

for x in [1, 2, 3, 4]:


print(x)

for i in range(10):
print(i, i*i, i*i*i)

The built-in func!on zip takes two lists and returns list of pairs.

>>> zip(["a", "b", "c"], [1, 2, 3])


[('a', 1), ('b', 2), ('c', 3)]
It is handy when we want to iterate over two lists together.

names = ["a", "b", "c"]


values = [1, 2, 3]
for name, value in zip(names, values):
print(name, value)

Problem 2: Python has a built-in func!on sum to find sum of all


elements of a list. Provide an implementa!on for sum .

>>> sum([1, 2, 3])


>>> 6

Problem 3: What happens when the above sum func!on is called with
a list of strings? Can you make your sum func!on work for a list of
strings as well.

>>> sum(["hello", "world"])


"helloworld"
>>> sum(["aa", "bb", "cc"])
"aabbcc"

Problem 4: Implement a func!on product , to compute product of a


list of numbers.

>>> product([1, 2, 3])


6

Problem 5: Write a func!on factorial to compute factorial of a


number. Can you use the product func!on defined in the previous
example to compute factorial?
>>> factorial(4)
24

Problem 6: Write a func!on reverse to reverse a list. Can you do this


without using list slicing?

>>> reverse([1, 2, 3, 4])


[4, 3, 2, 1]
>>> reverse(reverse([1, 2, 3, 4]))
[1, 2, 3, 4]

Problem 7: Python has built-in func!ons min and max to compute


minimum and maximum of a given list. Provide an implementa!on for
these func!ons. What happens when you call your min and max

func!ons with a list of strings?

Problem 8: Cumula!ve sum of a list [a, b, c, ...] is defined as [a,

a+b, a+b+c, ...] . Write a func!on cumulative_sum to compute


cumula!ve sum of a list. Does your implementa!on work for a list of
strings?

>>> cumulative_sum([1, 2, 3, 4])


[1, 3, 6, 10]
>>> cumulative_sum([4, 3, 2, 1])
[4, 7, 9, 10]

Problem 9: Write a func!on cumulative_product to compute


cumula!ve product of a list of numbers.

>>> cumulative_product([1, 2, 3, 4])


[1, 2, 6, 24]
>>> cumulative_product([4, 3, 2, 1])
[4, 12, 24, 24]
Problem 10: Write a func!on unique to find all the unique elements of
a list.

>>> unique([1, 2, 1, 3, 2, 5])


[1, 2, 3, 5]

Problem 11: Write a func!on dups to find all duplicates in the list.

>>> dups([1, 2, 1, 3, 2, 5])


[1, 2]

Problem 12: Write a func!on group(list, size) that take a list and splits
into smaller lists of given size.

>>> group([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)


[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> group([1, 2, 3, 4, 5, 6, 7, 8, 9], 4)
[[1, 2, 3, 4], [5, 6, 7, 8], [9]]

2.1.2. Sorting Lists

The sort method sorts a list in place.

>>> a = [2, 10, 4, 3, 7]


>>> a.sort()
>>> a
[2, 3, 4, 7 10]

The built-in func!on sorted returns a new sorted list without


modifying the source list.
>>> a = [4, 3, 5, 9, 2]
>>> sorted(a)
[2, 3, 4, 5, 9]
>>> a
[4, 3, 5, 9, 2]

The behavior of sort method and sorted func!on is exactly same


except that sorted returns a new list instead of modifying the given
list.

The sort method works even when the list has different types of
objects and even lists.

>>> a = ["hello", 1, "world", 45, 2]


>>> a.sort()
>>> a
[1, 2, 45, 'hello', 'world']
>>> a = [[2, 3], [1, 6]]
>>> a.sort()
>>> a
[[1, 6], [2, 3]]

We can op!onally specify a func!on as sort key.

>>> a = [[2, 3], [4, 6], [6, 1]]


>>> a.sort(key=lambda x: x[1])
>>> a
[[6, 1], [2, 3], [4 6]]

This sorts all the elements of the list based on the value of second
element of each entry.

Problem 13: Write a func!on lensort to sort a list of strings based on


length.
>>> lensort(['python', 'perl', 'java', 'c', 'haskell', 'ruby'])
['c', 'perl', 'java', 'ruby', 'python', 'haskell']

Problem 14: Improve the unique func!on wri"en in previous problems


to take an op!onal key func!on as argument and use the return value
of the key func!on to check for uniqueness.

>>> unique(["python", "java", "Python", "Java"], key=lambda s:


s.lower())
["python", "java"]

2.2. Tuples

Tuple is a sequence type just like list , but it is immutable. A tuple


consists of a number of values separated by commas.

>>> a = (1, 2, 3)
>>> a[0]
1

The enclosing braces are op!onal.

>>> a = 1, 2, 3
>>> a[0]
1

The built-in func!on len and slicing works on tuples too.

>>> len(a)
3
>>> a[1:]
2, 3
Since parenthesis are also used for grouping, tuples with a single value
are represented with an addi!onal comma.

>>> a = (1)
>> a
1
>>> b = (1,)
>>> b
(1,)
>>> b[0]
1

2.3. Sets

Sets are unordered collec!on of unique elements.

>>> x = set([3, 1, 2, 1])


set([1, 2, 3])

Python 2.7 introduced a new way of wri!ng sets.

>>> x = {3, 1, 2, 1}
set([1, 2, 3])

New elements can be added to a set using the add method.

>>> x = set([1, 2, 3])


>>> x.add(4)
>>> x
set([1, 2, 3, 4])

Just like lists, the existance of an element can be checked using the
in operator. However, this opera!on is faster in sets compared to
lists.
>>> x = set([1, 2, 3])
>>> 1 in x
True
>>> 5 in x
False

Problem 15: Reimplement the unique func!on implemented in the


earlier examples using sets.

2.4. Strings

Strings also behave like lists in many ways. Length of a string can be
found using built-in func!on len .

>>> len("abrakadabra")
11

Indexing and slicing on strings behave similar to that of lists.

>>> a = "helloworld"
>>> a[1]
'e'
>>> a[-2]
'l'
>>> a[1:5]
"ello"
>>> a[:5]
"hello"
>>> a[5:]
"world"
>>> a[-2:]
'ld'
>>> a[:-2]
'hellowor'
>>> a[::-1]
'dlrowolleh'

The in operator can be used to check if a string is present in another


string.
>>> 'hell' in 'hello'
True
>>> 'full' in 'hello'
False
>>> 'el' in 'hello'
True

There are many useful methods on strings.

The split method splits a string using a delimiter. If no delimiter is


specified, it uses any whitespace char as delimiter.

>>> "hello world".split()


['hello', 'world']
>>> "a,b,c".split(',')
['a', 'b', 'c']

The join method joins a list of strings.

>>> " ".join(['hello', 'world'])


'hello world'
>>> ','.join(['a', 'b', 'c'])

The strip method returns a copy of the given string with leading and
trailing whitespace removed. Op!onally a string can be passed as
argument to remove characters from that string instead of whitespace.

>>> ' hello world\n'.strip()


'hello world'
>>> 'abcdefgh'.strip('abdh')
'cdefg'

Python supports forma$ng values into strings. Although this can


include very complicated expressions, the most basic usage is to insert
values into a string with the %s placeholder.
>>> a = 'hello'
>>> b = 'python'
>>> "%s %s" % (a, b)
'hello python'
>>> 'Chapter %d: %s' % (2, 'Data Structures')
'Chapter 2: Data Structures'

Problem 16: Write a func!on extsort to sort a list of files based on


extension.

>>> extsort(['a.c', 'a.py', 'b.py', 'bar.txt', 'foo.txt',


'x.c'])
['a.c', 'x.c', 'a.py', 'b.py', 'bar.txt', 'foo.txt']

2.5. Working With Files

Python provides a built-in func!on open to open a file, which returns


a file object.

f = open('foo.txt', 'r') # open a file in read mode


f = open('foo.txt', 'w') # open a file in write mode
f = open('foo.txt', 'a') # open a file in append mode

The second argument to open is op!onal, which defaults to 'r'

when not specified.

Unix does not dis!nguish binary files from text files but windows does.
On windows 'rb' , 'wb' , 'ab' should be used to open a binary file
in read, write and append mode respec!vely.

Easiest way to read contents of a file is by using the read method.

>>> open('foo.txt').read()
'first line\nsecond line\nlast line\n'
Contents of a file can be read line-wise using readline and
readlines methods. The readline method returns empty string
when there is nothing more to read in a file.

>>> open('foo.txt').readlines()
['first line\n', 'second line\n', 'last line\n']
>>> f = open('foo.txt')
>>> f.readline()
'first line\n'
>>> f.readline()
'second line\n'
>>> f.readline()
'last line\n'
>>> f.readline()
''

The write method is used to write data to a file opened in write or


append mode.

>>> f = open('foo.txt', 'w')


>>> f.write('a\nb\nc')
>>> f.close()

>>> f.open('foo.txt', 'a')


>>> f.write('d\n')
>>> f.close()

The writelines method is convenient to use when the data is


available as a list of lines.

>>> f = open('foo.txt')
>>> f.writelines(['a\n', 'b\n', 'c\n'])
>>> f.close()

2.5.1. Example: Word Count

Lets try to compute the number of characters, words and lines in a file.
Number of characters in a file is same as the length of its contents.

def charcount(filename):
return len(open(filename).read())

Number of words in a file can be found by spli$ng the contents of the


file.

def wordcount(filename):
return len(open(filename).read().split())

Number of lines in a file can be found from readlines method.

def linecount(filename):
return len(open(filename).readlines())

Problem 17: Write a program reverse.py to print lines of a file in


reverse order.

$ cat she.txt
She sells seashells on the seashore;
The shells that she sells are seashells I'm sure.
So if she sells seashells on the seashore,
I'm sure that the shells are seashore shells.

$ python reverse.py she.txt


I'm sure that the shells are seashore shells.
So if she sells seashells on the seashore,
The shells that she sells are seashells I'm sure.
She sells seashells on the seashore;

Problem 18: Write a program to print each line of a file in reverse


order.
Problem 19: Implement unix commands head and tail . The head

and tail commands take a file as argument and prints its first and
last 10 lines of the file respec!vely.

Problem 20: Implement unix command grep . The grep command


takes a string and a file as arguments and prints all lines in the file
which contain the specified string.

$ python grep.py she.txt sure


The shells that she sells are seashells I'm sure.
I'm sure that the shells are seashore shells.

Problem 21: Write a program wrap.py that takes filename and width as
aruguments and wraps the lines longer than width.

$ python wrap.py she.txt 30


I'm sure that the shells are s
eashore shells.
So if she sells seashells on t
he seashore,
The shells that she sells are
seashells I'm sure.
She sells seashells on the sea
shore;

Problem 22: The above wrap program is not so nice because it is


breaking the line at middle of any word. Can you write a new program
wordwrap.py that works like wrap.py, but breaks the line only at the
word boundaries?
$ python wordwrap.py she.txt 30
I'm sure that the shells are
seashore shells.
So if she sells seashells on
the seashore,
The shells that she sells are
seashells I'm sure.
She sells seashells on the
seashore;

Problem 23: Write a program center_align.py to center align all lines in


the given file.

$ python center_align.py she.txt


I'm sure that the shells are seashore shells.
So if she sells seashells on the seashore,
The shells that she sells are seashells I'm sure.
She sells seashells on the seashore;

2.6. List Comprehensions

List Comprehensions provide a concise way of crea!ng lists. Many


!mes a complex task can be modelled in a single line.

Here are some simple examples for transforming a list.

>>> a = range(10)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [x for x in a]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [x*x for x in a]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> [x+1 for x in a]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

It is also possible to filter a list using if inside a list comprehension.


>>> a = range(10)
>>> [x for x in a if x % 2 == 0]
[0, 2, 4, 6, 8]
>>> [x*x for x in a if x%2 == 0]
[0, 4, 8, 36, 64]

It is possible to iterate over mul!ple lists using the built-in func!on


zip .

>>> a = [1, 2, 3, 4]
>>> b = [2, 3, 5, 7]
>>> zip(a, b)
[(1, 2), (2, 3), (3, 5), (4, 7)]
>>> [x+y for x, y in zip(a, b)]
[3, 5, 8, 11]

we can use mul!ple for clauses in single list comprehension.

>>> [(x, y) for x in range(5) for y in range(5) if (x+y)%2 == 0]


[(0, 0), (0, 2), (0, 4), (1, 1), (1, 3), (2, 0), (2, 2), (2, 4),
(3, 1), (3, 3), (4, 0), (4, 2), (4, 4)]

>>> [(x, y) for x in range(5) for y in range(5) if (x+y)%2 == 0


and x != y]
[(0, 2), (0, 4), (1, 3), (2, 0), (2, 4), (3, 1), (4, 0), (4, 2)]

>>> [(x, y) for x in range(5) for y in range(x) if (x+y)%2 == 0]


[(2, 0), (3, 1), (4, 0), (4, 2)]

The following example finds all Pythagorean triplets using numbers


below 25. (x, y, z) is a called pythagorean triplet if x*x + y*y ==

z*z .

>>> n = 25
>>> [(x, y, z) for x in range(1, n) for y in range(x, n) for z
in range(y, n) if x*x + y*y == z*z]
[(3, 4, 5), (5, 12, 13), (6, 8, 10), (8, 15, 17), (9, 12, 15),
(12, 16, 20)]
Problem 24: Provide an implementa!on for zip func!on using list
comprehensions.

>>> zip([1, 2, 3], ["a", "b", "c"])


[(1, "a"), (2, "b"), (3, "c")]

Problem 25: Python provides a built-in func!on map that applies a


func!on to each element of a list. Provide an implementa!on for map

using list comprehensions.

>>> def square(x): return x * x


...
>>> map(square, range(5))
[0, 1, 4, 9, 16]

Problem 26: Python provides a built-in func!on filter(f, a) that


returns items of the list a for which f(item) returns true. Provide an
implementa!on for filter using list comprehensions.

>>> def even(x): return x %2 == 0


...
>>> filter(even, range(10))
[0, 2, 4, 6, 8]

Problem 27: Write a func!on triplets that takes a number n as


argument and returns a list of triplets such that sum of first two
elements of the triplet equals the third element using numbers below
n. Please note that (a, b, c) and (b, a, c) represent same triplet.

>>> triplets(5)
[(1, 1, 2), (1, 2, 3), (1, 3, 4), (2, 2, 4)]
Problem 28: Write a func!on enumerate that takes a list and returns a
list of tuples containing (index,item) for each item in the list.

>>> enumerate(["a", "b", "c"])


[(0, "a"), (1, "b"), (2, "c")]
>>> for index, value in enumerate(["a", "b", "c"]):
... print(index, value)
0 a
1 b
2 c

Problem 29: Write a func!on array to create an 2-dimensional array.


The func!on should take both dimensions as arguments. Value of each
element can be ini!alized to None:

>>> a = array(2, 3)
>>> a
[[None, None, None], [None, None, None]]
>>> a[0][0] = 5
[[5, None, None], [None, None, None]]

Problem 30: Write a python func!on parse_csv to parse csv (comma


separated values) files.

>>> print(open('a.csv').read())
a,b,c
1,2,3
2,3,4
3,4,5
>>> parse_csv('a.csv')
[['a', 'b', 'c'], ['1', '2', '3'], ['2', '3', '4'], ['3', '4',
'5']]

Problem 31: Generalize the above implementa!on of csv parser to


support any delimiter and comments.
>>> print(open('a.txt').read())
# elements are separated by ! and comment indicator is #
a!b!c
1!2!3
2!3!4
3!4!5
>>> parse('a.txt', '!', '#')
[['a', 'b', 'c'], ['1', '2', '3'], ['2', '3', '4'], ['3', '4',
'5']]

Problem 32: Write a func!on mutate to compute all words generated


by a single muta!on on a given word. A muta!on is defined as
inser!ng a character, dele!ng a character, replacing a character, or
swapping 2 consecu!ve characters in a string. For simplicity consider
only le"ers from a to z .

>>> words = mutate('hello')


>>> 'helo' in words
True
>>> 'cello' in words
True
>>> 'helol' in words
True

Problem 33: Write a func!on nearly_equal to test whether two


strings are nearly equal. Two strings a and b are nearly equal when
a can be generated by a single muta!on on b .

>>> nearly_equal('python', 'perl')


False
>>> nearly_equal('perl', 'pearl')
True
>>> nearly_equal('python', 'jython')
True
>>> nearly_equal('man', 'woman')
False

2.7. Dictionaries
Dic!onaries are like lists, but they can be indexed with non integer
keys also. Unlike lists, dic!onaries are not ordered.

>>> a = {'x': 1, 'y': 2, 'z': 3}


>>> a['x']
1
>>> a['z']
3
>>> b = {}
>>> b['x'] = 2
>>> b[2] = 'foo'
>>> b[(1, 2)] = 3
>>> b
{(1, 2): 3, 'x': 2, 2: 'foo'}

The del keyword can be used to delete an item from a dic!onary.

>>> a = {'x': 1, 'y': 2, 'z': 3}


>>> del a['x']
>>> a
{'y': 2, 'z': 3}

The keys method returns all keys in a dic!onary, the values method
returns all values in a dic!onary and items method returns all key-
value pairs in a dic!onary.

>>> a.keys()
['x', 'y', 'z']
>>> a.values()
[1, 2, 3]
>>> a.items()
[('x', 1), ('y', 2), ('z', 3)]

The for statement can be used to iterate over a dic!onary.


>>> for key in a: print(key)
...
x
y
z
>>> for key, value in a.items(): print(key, value)
...
x 1
y 2
z 3

Presence of a key in a dic!onary can be tested using in operator or


has_key method.

>>> 'x' in a
True
>>> 'p' in a
False
>>> a.has_key('x')
True
>>> a.has_key('p')
False

Other useful methods on dic!onaries are get and setdefault .

>>> d = {'x': 1, 'y': 2, 'z': 3}


>>> d.get('x', 5)
1
>>> d.get('p', 5)
5
>>> d.setdefault('x', 0)
1
>>> d
{'x': 1, 'y': 2, 'z': 3}
>>> d.setdefault('p', 0)
0
>>> d
{'y': 2, 'x': 1, 'z': 3, 'p': 0}

Dic!onaries can be used in string forma$ng to specify named


parameters.
>>> 'hello %(name)s' % {'name': 'python'}
'hello python'
>>> 'Chapter %(index)d: %(name)s' % {'index': 2, 'name': 'Data
Structures'}
'Chapter 2: Data Structures'

2.7.1. Example: Word Frequency

Suppose we want to find number of occurrences of each word in a file.


Dic!onary can be used to store the number of occurrences for each
word.

Lets first write a func!on to count frequency of words, given a list of


words.

def word_frequency(words):
"""Returns frequency of each word given a list of words.

>>> word_frequency(['a', 'b', 'a'])


{'a': 2, 'b': 1}
"""
frequency = {}
for w in words:
frequency[w] = frequency.get(w, 0) + 1
return frequency

Ge$ng words from a file is very trivial.

def read_words(filename):
return open(filename).read().split()

We can combine these two func!ons to find frequency of all words in


a file.
def main(filename):
frequency = word_frequency(read_words(filename))
for word, count in frequency.items():
print(word, count)

if __name__ == "__main__":
import sys
main(sys.argv[1])

Problem 34: Improve the above program to print the words in the
descending order of the number of occurrences.

Problem 35: Write a program to count frequency of characters in a


given file. Can you use character frequency to tell whether the given
file is a Python program file, C program file or a text file?

Problem 36: Write a program to find anagrams in a given list of words.


Two words are called anagrams if one word can be formed by
rearranging le"ers of another. For example 'eat', 'ate' and 'tea' are
anagrams.

>>> anagrams(['eat', 'ate', 'done', 'tea', 'soup', 'node'])


[['eat', 'ate', 'tea], ['done', 'node'], ['soup']]

Problem 37: Write a func!on valuesort to sort values of a dic!onary


based on the key.

>>> valuesort({'x': 1, 'y': 2, 'a': 3})


[3, 1, 2]

Problem 38: Write a func!on invertdict to interchange keys and


values in a dic!onary. For simplicity, assume that all values are unique.

>>> invertdict({'x': 1, 'y': 2, 'z': 3})


{1: 'x', 2: 'y', 3: 'z'}
2.7.2. Understanding Python Execution
Environment

Python stores the variables we use as a dic!onary. The globals()

func!on returns all the globals variables in the current environment.

>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__':
'__main__', '__doc__': None}
>>> x = 1
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__':
'__main__', '__doc__': None, 'x': 1}
>>> x = 2
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__':
'__main__', '__doc__': None, 'x': 2}
>>> globals()['x'] = 3
>>> x
3

Just like globals python also provides a func!on locals which gives
all the local variables in a func!on.

>>> def f(a, b): print(locals())


...
>>> f(1, 2)
{'a': 1, 'b': 2}

One more example:

>>> def f(name):


... return "Hello %(name)s!" % locals()
...
>>> f("Guido")
Hello Guido!
Further Reading:

The ar!cle A Plan for Spam by Paul Graham describes a method of


detec!ng spam using probability of occurrence of a word in spam.
! » 3. Modules

3. Modules
Modules are reusable libraries of code in Python. Python comes with
many standard library modules.

A module is imported using the import statement.

>>> import time


>>> print(time.asctime())
'Fri Mar 30 12:59:21 2012'

In this example, we’ve imported the !me module and called the
asc!me func!on from that module, which returns current !me as a
string.

There is also another way to use the import statement.

>>> from time import asctime


>>> asctime()
'Fri Mar 30 13:01:37 2012'

Here were imported just the asc!me func!on from the !me module.

The pydoc command provides help on any module or a func!on.


$ pydoc time
Help on module time:

NAME
time - This module provides various functions to manipulate
time values.
...

$ pydoc time.asctime
Help on built-in function asctime in time:

time.asctime = asctime(...)
asctime([tuple]) -> string
...

On Windows, the pydoc command is not available. The work-around is


to use, the built-in help func!on.

>>> help('time')
Help on module time:

NAME
time - This module provides various functions to manipulate
time values.
...

Wri!ng our own modules is very simple.

For example, create a file called num.py with the following content.

def square(x):
return x * x

def cube(x):
return x * x * x

Now open Python interterter:


>>> import num
>>> num.square(3)
9
>>> num.cube(3)
27

Thats all we’ve wri"en a python library.

Try pydoc num (pydoc.bat numbers on Windows) to see documenta!on


for this numbers modules. It won’t have any documenta!on as we
haven’t providied anything yet.

In Python, it is possible to associate documenta!on for each module,


func!on using docstrings. Docstrings are strings wri"en at the top of
the module or at the beginning of a func!on.

Lets try to document our num module by changing the contents of


num.py

"""The num module provides utilties to work on numbers.

Current it provides square and cube.


"""

def square(x):
"""Computes square of a number."""
return x * x

def cube(x):
"""Computes cube of a number."""
return x * x

The pydoc command will now show us the doumenta!on nicely


forma"ed.
Help on module num:

NAME
num - The num module provides utilties to work on numbers.

FILE
/Users/anand/num.py

DESCRIPTION
Current it provides square and cube.

FUNCTIONS
cube(x)
Computes cube of a number.

square(x)
Computes square of a number.

Under the hood, python stores the documenta!on as a special field


called __doc__.

>>> import os
>>> print(os.getcwd.__doc__)
getcwd() -> path

Return a string representing the current working directory.

3.1. Standard Library

Python comes with many standard library modules. Lets look at some
of the most commonly used ones.

3.1.1. os module

The os and os.path modules provides func!onality to work with files,


directories etc.

Problem 1: Write a program to list all files in the given directory.


Problem 2: Write a program extcount.py to count number of files for
each extension in the given directory. The program should take a
directory name as argument and print count and extension for each
available file extension.

$ python extcount.py src/


14 py
4 txt
1 csv

Problem 3: Write a program to list all the files in the given directory
along with their length and last modifica!on !me. The output should
contain one line for each file containing filename, length and
modifica!on date separated by tabs.

Hint: see help for os.stat .

Problem 4: Write a program to print directory tree. The program


should take path of a directory as argument and print all the files in it
recursively as a tree.

$ python dirtree.py foo


foo
|-- a.txt
|-- b.txt
|-- code
| |-- a.py
| |-- b.py
| |-- docs
| | |-- a.txt
| | \-- b.txt
| \-- x.py
\-- z.txt

3.1.2. urllib module

The urllib module provides func!onality to download webpages.


>>> from urllib.request import urlopen
>>> response = urlopen("http://python.org/")
>>> print(response.headers)
Date: Fri, 30 Mar 2012 09:24:55 GMT
Server: Apache/2.2.16 (Debian)
Last-Modified: Fri, 30 Mar 2012 08:42:25 GMT
ETag: "105800d-4b7b-4bc71d1db9e40"
Accept-Ranges: bytes
Content-Length: 19323
Connection: close
Content-Type: text/html
X-Pad: avoid browser bug

>>> response.header['Content-Type']
'text/html'

>>> content = request.read()

Problem 5: Write a program wget.py to download a given URL. The


program should accept a URL as argument, download it and save it
with the basename of the URL. If the URL ends with a /, consider the
basename as index.html.

$ python wget.py
http://docs.python.org/tutorial/interpreter.html
saving http://docs.python.org/tutorial/interpreter.html as
interpreter.html.

$ python wget.py http://docs.python.org/tutorial/


saving http://docs.python.org/tutorial/ as index.html.

3.1.3. re module

Problem 6: Write a program an!html.py that takes a URL as argument,


downloads the html from web and print it a#er stripping html tags.
$ python antihtml.py index.html
...
The Python interpreter is usually installed as
/usr/local/bin/python on
those machines where it is available; putting /usr/local/bin in
your
...

Problem 7: Write a func!on make_slug that takes a name converts it


into a slug. A slug is a string where spaces and special characters are
replaced by a hyphen, typically used to create blog post URL from post
!tle. It should also make sure there are no more than one hyphen in
any place and there are no hyphens at the biginning and end of the
slug.

>>> make_slug("hello world")


'hello-world'
>>> make_slug("hello world!")
'hello-world'
>>> make_slug(" --hello- world--")
'hello-world'

Problem 8: Write a program links.py that takes URL of a webpage as


argument and prints all the URLs linked from that webpage.

Problem 9: Write a regular expression to validate a phone number.

3.1.4. json module

Problem 10: Write a program myip.py to print the external IP address


of the machine. Use the response from http://httpbin.org/get and
read the IP address from there. The program should print only the IP
address and nothing else.

3.1.5. zipfile module


The zipfile module provides interface to read and write zip files.

Here are some examples to demonstate the power of zipfile module.

The following example prints names of all the files in a zip archive.

import zipfile
z = zipfile.ZipFile("a.zip")
for name in z.namelist():
print(name)

The following example prints each file in the zip archive.

import zipfile
z = zipfile.ZipFile("a.zip")
for name in z.namelist():
print()
print("FILE:", name)
print()
print(z.read(name))

Problem 11: Write a python program zip.py to create a zip file. The
program should take name of zip file as first argument and files to add
as rest of the arguments.

$ python zip.py foo.zip file1.txt file2.txt

Problem 12: Write a program mydoc.py to implement the func!onality


of pydoc. The program should take the module name as argument and
print documenta!on for the module and each of the func!ons defined
in that module.
$ python mydoc.py os
Help on module os:

DESCRIPTION

os - OS routines for Mac, NT, or Posix depending on what system


we're on.
...

FUNCTIONS

getcwd()
...

Hints:

The dir func!on to get all entries of a module


The inspect.isfunc!on func!on can be used to test if given
object is a func!on
x.__doc__ gives the docstring for x.
The __import__ func!on can be used to import a module by
name

3.2. Installing third-party modules

PyPI, The Python Package Index maintains the list of Python packages
available. The third-party module developers usually register at PyPI
and uploads their packages there.

The standard way to installing a python module is using pip or


easy_install. Pip is more modern and perferred.

Lets start with installing easy_install.

Download the easy_install install script ez_setup.py.


Run it using Python.
That will install easy_install , the script used to install third-party
python packages.

Before installing new packages, lets understand how to manage virtual


environments for installing python packages.

Earlier the only way of installing python packages was system wide.
When used this way, packages installed for one project can conflict
with other and create trouble. So people invented a way to create
isolated Python environment to install packages. This tool is called
virtualenv.

To install virtualenv :

$ easy_install virtualenv

Installing virtualenv also installs the pip command, a be"er replace for
easy_install.

Once it is installed, create a new virtual env by running the


virtualenv command.

$ virtualenv testenv

Now to switch to that env.

On UNIX/Mac OS X:

$ source testenv/bin/activate

On Windows:
> testenv\Scripts\activate

Now the virtualenv testenv is ac!vated.

Now all the packages installed will be limited to this virtualenv. Lets try
to install a third-party package.

$ pip install tablib

This installs a third-party library called tablib .

The tablib library is a small li"le library to work with tabular data and
write csv and Excel files.

Here is a simple example.

# create a dataset
data = tablib.Dataset()

# Add rows
data.append(["A", 1])
data.append(["B", 2])
data.append(["C", 3])

# save as csv
with open('test.csv', 'wb') as f:
f.write(data.csv)

# save as Excel
with open('test.xls', 'wb') as f:
f.write(data.xls)

# save as Excel 07+


with open('test.xlsx', 'wb') as f:
f.write(data.xlsx)

It is even possible to create mul!-sheet excel files.


sheet1 = tablib.Dataset()
sheet1.append(["A1", 1])
sheet1.append(["A2", 2])

sheet2 = tablib.Dataset()
sheet2.append(["B1", 1])
sheet2.append(["B2", 2])

book = tablib.Databook([data1, data2])


with open('book.xlsx', 'wb') as f:
f.write(book.xlsx)

Problem 13: Write a program csv2xls.py that reads a csv file and
exports it as Excel file. The prigram should take two arguments. The
name of the csv file to read as first argument and the name of the
Excel file to write as the second argument.

Problem 14: Create a new virtualenv and install Beau!fulSoup.


Beau!fulSoup is very good library for parsing HTML. Try using it to
extract all HTML links from a webpage.

Read the Beau!fulSoup documenta!on to get started.


! » 4. Object Oriented Programming

4. Object Oriented Programming

4.1. State

Suppose we want to model a bank account with support for deposit

and withdraw opera!ons. One way to do that is by using global state


as shown in the following example.

balance = 0

def deposit(amount):
global balance
balance += amount
return balance

def withdraw(amount):
global balance
balance -= amount
return balance

The above example is good enough only if we want to have just a


single account. Things start ge"ng complicated if want to model
mul!ple accounts.

We can solve the problem by making the state local, probably by using
a dic!onary to store the state.
def make_account():
return {'balance': 0}

def deposit(account, amount):


account['balance'] += amount
return account['balance']

def withdraw(account, amount):


account['balance'] -= amount
return account['balance']

With this it is possible to work with mul!ple accounts at the same


!me.

>>> a = make_account()
>>> b = make_account()
>>> deposit(a, 100)
100
>>> deposit(b, 50)
50
>>> withdraw(b, 10)
40
>>> withdraw(a, 10)
90

4.2. Classes and Objects


class BankAccount:
def __init__(self):
self.balance = 0

def withdraw(self, amount):


self.balance -= amount
return self.balance

def deposit(self, amount):


self.balance += amount
return self.balance

>>> a = BankAccount()
>>> b = BankAccount()
>>> a.deposit(100)
100
>>> b.deposit(50)
50
>>> b.withdraw(10)
40
>>> a.withdraw(10)
90

4.3. Inheritance

Let us try to create a li#le more sophis!cated account type where the
account holder has to maintain a pre-determined minimum balance.

class MinimumBalanceAccount(BankAccount):
def __init__(self, minimum_balance):
BankAccount.__init__(self)
self.minimum_balance = minimum_balance

def withdraw(self, amount):


if self.balance - amount < self.minimum_balance:
print('Sorry, minimum balance must be maintained.')
else:
BankAccount.withdraw(self, amount)

Problem 1: What will the output of the following program.


class A:
def f(self):
return self.g()

def g(self):
return 'A'

class B(A):
def g(self):
return 'B'

a = A()
b = B()
print(a.f(), b.f())
print(a.g(), b.g())

Example: Drawing Shapes


class Canvas:
def __init__(self, width, height):
self.width = width
self.height = height
self.data = [[' '] * width for i in range(height)]

def setpixel(self, row, col):


self.data[row][col] = '*'

def getpixel(self, row, col):


return self.data[row][col]

def display(self):
print("\n".join(["".join(row) for row in self.data]))

class Shape:
def paint(self, canvas): pass

class Rectangle(Shape):
def __init__(self, x, y, w, h):
self.x = x
self.y = y
self.w = w
self.h = h

def hline(self, x, y, w):


pass

def vline(self, x, y, h):


pass

def paint(self, canvas):


hline(self.x, self.y, self.w)
hline(self.x, self.y + self.h, self.w)
vline(self.x, self.y, self.h)
vline(self.x + self.w, self.y, self.h)

class Square(Rectangle):
def __init__(self, x, y, size):
Rectangle.__init__(self, x, y, size, size)

class CompoundShape(Shape):
def __init__(self, shapes):
self.shapes = shapes

def paint(self, canvas):


for s in self.shapes:
s.paint(canvas)
4.4. Special Class Methods

In Python, a class can implement certain opera!ons that are invoked


by special syntax (such as arithme!c opera!ons or subscrip!ng and
slicing) by defining methods with special names. This is Python’s
approach to operator overloading, allowing classes to define their own
behavior with respect to language operators.

For example, the + operator invokes __add__ method.

>>> a, b = 1, 2
>>> a + b
3
>>> a.__add__(b)
3

Just like __add__ is called for + operator, __sub__ , __mul__ and


__div__ methods are called for - , * , and / operators.

Example: Ra!onal Numbers

Suppose we want to do arithme!c with ra!onal numbers. We want to


be able to add, subtract, mul!ply, and divide them and to test whether
two ra!onal numbers are equal.

We can add, subtract, mul!ply, divide, and test equality by using the
following rela!ons:

n1/d1 + n2/d2 = (n1*d2 + n2*d1)/(d1*d2)


n1/d1 - n2/d2 = (n1*d2 - n2*d1)/(d1*d2)
n1/d1 * n2/d2 = (n1*n2)/(d1*d2)
(n1/d1) / (n2/d2) = (n1*d2)/(d1*n2)

n1/d1 == n2/d2 if and only if n1*d2 == n2*d1


Lets write the ra!onal number class.

class RationalNumber:
"""
Rational Numbers with support for arthmetic operations.

>>> a = RationalNumber(1, 2)
>>> b = RationalNumber(1, 3)
>>> a + b
5/6
>>> a - b
1/6
>>> a * b
1/6
>>> a/b
3/2
"""
def __init__(self, numerator, denominator=1):
self.n = numerator
self.d = denominator

def __add__(self, other):


if not isinstance(other, RationalNumber):
other = RationalNumber(other)

n = self.n * other.d + self.d * other.n


d = self.d * other.d
return RationalNumber(n, d)

def __sub__(self, other):


if not isinstance(other, RationalNumber):
other = RationalNumber(other)

n1, d1 = self.n, self.d


n2, d2 = other.n, other.d
return RationalNumber(n1*d2 - n2*d1, d1*d2)

def __mul__(self, other):


if not isinstance(other, RationalNumber):
other = RationalNumber(other)

n1, d1 = self.n, self.d


n2, d2 = other.n, other.d
return RationalNumber(n1*n2, d1*d2)

def __div__(self, other):


if not isinstance(other, RationalNumber):
other = RationalNumber(other)

n1, d1 = self.n, self.d


n2, d2 = other.n, other.d
return RationalNumber(n1*d2, d1*n2)

def __str__(self):
return "%s/%s" % (self.n, self.d)

__repr__ = __str__

4.5. Errors and Exceptions

We’ve already seen excep!ons in various places. Python gives


NameError when we try to use a variable that is not defined.

>>> foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined

try adding a string to an integer:

>>> "foo" + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects

try dividing a number by 0:

>>> 2/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

or, try opening a file that is not there:


>>> open("not-there.txt")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'not-there.txt'

Python raises excep!on in case errors. We can write programs to


handle such errors. We too can raise excep!ons when an error case in
encountered.

Excep!ons are handled by using the try-except statements.

def main():
filename = sys.argv[1]
try:
for row in parse_csv(filename):
print row
except IOError:
print("The given file doesn't exist: ", filename,
file=sys.stderr)
sys.exit(1)

This above example prints an error message and exits with an error
status when an IOError is encountered.

The except statement can be wri#en in mul!ple ways:


# catch all exceptions
try:
...
except:

# catch just one exception


try:
...
except IOError:
...

# catch one exception, but provide the exception object


try:
...
except IOError as e:
...

# catch more than one exception


try:
...
except (IOError, ValueError) as e:
...

It is possible to have more than one except statements with one try.

try:
...
except IOError as e:
print("Unable to open the file (%s): %s" % (str(e),
filename), file=sys.stderr)
sys.exit(1)
except FormatError as e:
print("File is badly formatted (%s): %s" % (str(e),
filename), file=sys.stderr)

The try statement can have an op!onal else clause, which is executed
only if no excep!on is raised in the try-block.
try:
...
except IOError as e:
print("Unable to open the file (%s): %s" % (str(e),
filename), file=sys.stderr)
sys.exit(1)
else:
print("successfully opened the file", filename)

There can be an op!onal else clause with a try statement, which is


executed irrespec!ve of whether or not excep!on has occured.

try:
...
except IOError as e:
print("Unable to open the file (%s): %s" % (str(e),
filename), file=sys.stderr)
sys.exit(1)
finally:
delete_temp_files()

Excep!on is raised using the raised keyword.

raise Exception("error message")

All the excep!ons are extended from the built-in Excep!on class.

class ParseError(Excep!on):

pass

Problem 2: What will be the output of the following program?


try:
print "a"
except:
print "b"
else:
print "c"
finally:
print "d"

Problem 3: What will be the output of the following program?

try:
print("a")
raise Exception("doom")
except:
print("b")
else:
print("c")
finally:
print("d")

Problem 4: What will be the output of the following program?

def f():
try:
print("a")
return
except:
print("b")
else:
print("c")
finally:
print("d")

f()
! » 5. Iterators & Generators

5. Iterators & Generators

5.1. Iterators

We use for statement for looping over a list.

>>> for i in [1, 2, 3, 4]:


... print(i)
...
1
2
3
4

If we use it with a string, it loops over its characters.

>>> for c in "python":


... print(c)
...
p
y
t
h
o
n

If we use it with a dic!onary, it loops over its keys.

>>> for k in {"x": 1, "y": 2}:


... print(k)
...
y
x
If we use it with a file, it loops over lines of the file.

>>> for line in open("a.txt"):


... print(line, end="")
...
first line
second line

So there are many types of objects which can be used with a for loop.
These are called iterable objects.

There are many func!ons which consume these iterables.

>>> ",".join(["a", "b", "c"])


'a,b,c'
>>> ",".join({"x": 1, "y": 2})
'y,x'
>>> list("python")
['p', 'y', 't', 'h', 'o', 'n']
>>> list({"x": 1, "y": 2})
['y', 'x']

5.1.1. The Iteration Protocol

The built-in func!on iter takes an iterable object and returns an


iterator.

>>> x = iter([1, 2, 3])


>>> x
<listiterator object at 0x1004ca850>
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Each !me we call the next method on the iterator gives us the next
element. If there are no more elements, it raises a StopItera!on.

Iterators are implemented as classes. Here is an iterator that works like


built-in range func!on.

class yrange:
def __init__(self, n):
self.i = 0
self.n = n

def __iter__(self):
return self

def __next__(self):
if self.i < self.n:
i = self.i
self.i += 1
return i
else:
raise StopIteration()

The __iter__ method is what makes an object iterable. Behind the


scenes, the iter func!on calls __iter__ method on the given object.

The return value of __iter__ is an iterator. It should have a __next__

method and raise StopIteration when there are no more elements.

Lets try it out:


>>> y = yrange(3)
>>> next(y)
0
>>> next(y)
1
>>> next(y)
2
>>> next(y)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 14, in __next__
StopIteration

Many built-in func!ons accept iterators as arguments.

>>> list(yrange(5))
[0, 1, 2, 3, 4]
>>> sum(yrange(5))
10

In the above case, both the iterable and iterator are the same object.
No!ce that the __iter__ method returned self . It need not be the
case always.
class zrange:
def __init__(self, n):
self.n = n

def __iter__(self):
return zrange_iter(self.n)

class zrange_iter:
def __init__(self, n):
self.i = 0
self.n = n

def __iter__(self):
# Iterators are iterables too.
# Adding this functions to make them so.
return self

def __next__(self):
if self.i < self.n:
i = self.i
self.i += 1
return i
else:
raise StopIteration()

If both iteratable and iterator are the same object, it is consumed in a


single itera!on.

>>> y = yrange(5)
>>> list(y)
[0, 1, 2, 3, 4]
>>> list(y)
[]
>>> z = zrange(5)
>>> list(z)
[0, 1, 2, 3, 4]
>>> list(z)
[0, 1, 2, 3, 4]

Problem 1: Write an iterator class reverse_iter , that takes a list and


iterates it from the reverse direc!on. ::
>>> it = reverse_iter([1, 2, 3, 4])
>>> next(it)
4
>>> next(it)
3
>>> next(it)
2
>>> next(it)
1
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

5.2. Generators

Generators simplifies crea!on of iterators. A generator is a func!on


that produces a sequence of results instead of a single value.

def yrange(n):
i = 0
while i < n:
yield i
i += 1

Each !me the yield statement is executed the func!on generates a


new value.

>>> y = yrange(3)
>>> y
<generator object yrange at 0x401f30>
>>> next(y)
0
>>> next(y)
1
>>> next(y)
2
>>> next(y)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
So a generator is also an iterator. You don’t have to worry about the
iterator protocol.

The word “generator” is confusingly used to mean both the func!on


that generates and what it generates. In this chapter, I’ll use the word
“generator” to mean the genearted object and “generator func!on” to
mean the func!on that generates it.

Can you think about how it is working internally?

When a generator func!on is called, it returns a generator object


without even beginning execu!on of the func!on. When next

method is called for the first !me, the func!on starts execu!ng un!l it
reaches yield statement. The yielded value is returned by the next

call.

The following example demonstrates the interplay between yield

and call to __next__ method on generator object.


>>> def foo():
... print("begin")
... for i in range(3):
... print("before yield", i)
... yield i
... print("after yield", i)
... print("end")
...
>>> f = foo()
>>> next(f)
begin
before yield 0
0
>>> next(f)
after yield 0
before yield 1
1
>>> next(f)
after yield 1
before yield 2
2
>>> next(f)
after yield 2
end
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>

Lets see an example:


def integers():
"""Infinite sequence of integers."""
i = 1
while True:
yield i
i = i + 1

def squares():
for i in integers():
yield i * i

def take(n, seq):


"""Returns first n values from the given sequence."""
seq = iter(seq)
result = []
try:
for i in range(n):
result.append(next(seq))
except StopIteration:
pass
return result

print(take(5, squares())) # prints [1, 4, 9, 16, 25]

5.3. Generator Expressions

Generator Expressions are generator version of list comprehensions.


They look like list comprehensions, but returns a generator back
instead of a list.

>>> a = (x*x for x in range(10))


>>> a
<generator object <genexpr> at 0x401f08>
>>> sum(a)
285

We can use the generator expressions as arguments to various


func!ons that consume iterators.

>>> sum((x*x for x in range(10)))


285
When there is only one argument to the calling func!on, the
parenthesis around generator expression can be omi"ed.

>>> sum(x*x for x in range(10))


285

Another fun example:

Lets say we want to find first 10 (or any n) pythogorian triplets. A


triplet (x, y, z) is called pythogorian triplet if x*x + y*y == z*z .

It is easy to solve this problem if we know !ll what value of z to test


for. But we want to find first n pythogorian triplets.

>>> pyt = ((x, y, z) for z in integers() for y in range(1, z)


for x in range(1, y) if x*x + y*y == z*z)
>>> take(10, pyt)
[(3, 4, 5), (6, 8, 10), (5, 12, 13), (9, 12, 15), (8, 15, 17),
(12, 16, 20), (15, 20, 25), (7, 24, 25), (10, 24, 26), (20, 21,
29)]

5.3.1. Example: Reading multiple files

Lets say we want to write a program that takes a list of filenames as


arguments and prints contents of all those files, like cat command in
unix.

The tradi!onal way to implement it is:

def cat(filenames):
for f in filenames:
for line in open(f):
print(line, end="")
Now, lets say we want to print only the line which has a par!cular
substring, like grep command in unix.

def grep(pattern, filenames):


for f in filenames:
for line in open(f):
if pattern in line:
print(line, end="")

Both these programs have lot of code in common. It is hard to move


the common part to a func!on. But with generators makes it possible
to do it.

def readfiles(filenames):
for f in filenames:
for line in open(f):
yield line

def grep(pattern, lines):


return (line for line in lines if pattern in line)

def printlines(lines):
for line in lines:
print(line, end="")

def main(pattern, filenames):


lines = readfiles(filenames)
lines = grep(pattern, lines)
printlines(lines)

The code is much simpler now with each func!on doing one small
thing. We can move all these func!ons into a separate module and
reuse it in other programs.

Problem 2: Write a program that takes one or more filenames as


arguments and prints all the lines which are longer than 40 characters.
Problem 3: Write a func!on findfiles that recursively descends the
directory tree for the specified directory and generates paths of all the
files in the tree.

Problem 4: Write a func!on to compute the number of python files


(.py extension) in a specified directory recursively.

Problem 5: Write a func!on to compute the total number of lines of


code in all python files in the specified directory recursively.

Problem 6: Write a func!on to compute the total number of lines of


code, ignoring empty and comment lines, in all python files in the
specified directory recursively.

Problem 7: Write a program split.py , that takes an integer n and a


filename as command line arguments and splits the file into mul!ple
small files with each having n lines.

5.4. Itertools

The itertools module in the standard library provides lot of inters!ng


tools to work with iterators.

Lets look at some of the interes!ng func!ons.

chain – chains mul!ple iterators together.

>>> it1 = iter([1, 2, 3])


>>> it2 = iter([4, 5, 6])
>>> itertools.chain(it1, it2)
[1, 2, 3, 4, 5, 6]

izip – iterable version of zip


>>> for x, y in itertools.izip(["a", "b", "c"], [1, 2, 3]):
... print(x, y)
...
a 1
b 2
c 3

Problem 8: Write a func!on peep , that takes an iterator as argument


and returns the first element and an equivalant iterator.

>>> it = iter(range(5))
>>> x, it1 = peep(it)
>>> print(x, list(it1))
0 [0, 1, 2, 3, 4]

Problem 9: The built-in func!on enumerate takes an iteratable and


returns an iterator over pairs (index, value) for each value in the
source.

>>> list(enumerate(["a", "b", "c"])


[(0, "a"), (1, "b"), (2, "c")]
>>> for i, c in enumerate(["a", "b", "c"]):
... print(i, c)
...
0 a
1 b
2 c

Write a func!on my_enumerate that works like enumerate .

Problem 10: Implement a func!on izip that works like


itertools.izip .

Further Reading
Generator Tricks For System Programers by David Beazly is an
excellent in-depth introduc!on to generators and generator
expressions.
! » 6. Func!onal Programming

6. Functional Programming

6.1. Recursion

Defining solu!on of a problem in terms of the same problem, typically


of smaller size, is called recursion. Recursion makes it possible to
express solu!on of a problem very concisely and elegantly.

A func!on is called recursive if it makes call to itself. Typically, a


recursive func!on will have a termina!ng condi!on and one or more
recursive calls to itself.

6.1.1. Example: Computing Exponent

Mathema!cally we can define exponent of a number in terms of its


smaller power.

def exp(x, n):


"""
Computes the result of x raised to the power of n.

>>> exp(2, 3)
8
>>> exp(3, 2)
9
"""
if n == 0:
return 1
else:
return x * exp(x, n-1)

Lets look at the execu!on pa"ern.


exp(2, 4)
+-- 2 * exp(2, 3)
| +-- 2 * exp(2, 2)
| | +-- 2 * exp(2, 1)
| | | +-- 2 * exp(2, 0)
| | | | +-- 1
| | | +-- 2 * 1
| | | +-- 2
| | +-- 2 * 2
| | +-- 4
| +-- 2 * 4
| +-- 8
+-- 2 * 8
+-- 16

Number of calls to the above exp func!on is propor!onal to size of


the problem, which is n here.

We can compute exponent in fewer steps if we use successive


squaring.

def fast_exp(x, n):


if n == 0:
return 1
elif n % 2 == 0:
return fast_exp(x*x, n/2))
else:
return x * fast_exp(x, n-1)

Lets look at the execu!on pa"ern now.


fast_exp(2, 10)
+-- fast_exp(4, 5) # 2 * 2
| +-- 4 * fast_exp(4, 4)
| | +-- fast_exp(16, 2) # 4 * 4
| | | +-- fast_exp(256, 1) # 16 * 16
| | | | +-- 256 * fast_exp(256, 0)
| | | | +-- 1
| | | | +-- 256 * 1
| | | | +-- 256
| | | +-- 256
| | +-- 256
| +-- 4 * 256
| +-- 1024
+-- 1024
1024

Problem 1: Implement a func!on product to mul!ply 2 numbers


recursively using + and - operators only.

6.1.2. Example: Flatten a list

Supposed you have a nested list and want to fla"en it.

def flatten_list(a, result=None):


"""Flattens a nested list.

>>> flatten_list([ [1, 2, [3, 4] ], [5, 6], 7])


[1, 2, 3, 4, 5, 6, 7]
"""
if result is None:
result = []

for x in a:
if isinstance(x, list):
flatten_list(x, result)
else:
result.append(x)

return result

Problem 2: Write a func!on flatten_dict to fla"en a nested


dic!onary by joining the keys with . character.
>>> flatten_dict({'a': 1, 'b': {'x': 2, 'y': 3}, 'c': 4})
{'a': 1, 'b.x': 2, 'b.y': 3, 'c': 4}

Problem 3: Write a func!on unflatten_dict to do reverse of


flatten_dict .

>>> unflatten_dict({'a': 1, 'b.x': 2, 'b.y': 3, 'c': 4})


{'a': 1, 'b': {'x': 2, 'y': 3}, 'c': 4}

Problem 4: Write a func!on treemap to map a func!on over nested


list.

>>> treemap(lambda x: x*x, [1, 2, [3, 4, [5]]])


[1, 4, [9, 16, [25]]]

Problem 5: Write a func!on tree_reverse to reverse elements of a


nested-list recursively.

>>> tree_reverse([[1, 2], [3, [4, 5]], 6])


[6, [[5, 4], 3], [2, 1]]

6.1.3. Example: JSON Encode

Lets look at more commonly used example of serializing a python


datastructure into JSON (JavaScript Object Nota!on).

Here is an example of JSON record.


{
"name": "Advanced Python Training",
"date": "October 13, 2012",
"completed": false,
"instructor": {
"name": "Anand Chitipothu",
"website": "http://anandology.com/"
},
"participants": [
{
"name": "Participant 1",
"email": "[email protected]"
},
{
"name": "Participant 2",
"email": "[email protected]"
}
]
}

It looks very much like Python dic!onaries and lists. There are some
differences though. Strings are always enclosed in double quotes,
booleans are represented as true and false .

The standard library module json provides func!onality to work in


JSON. Lets try to implement it now as it is very good example of use of
recursion.

For simplicity, lets assume that strings will not have any special
characters and can have space, tab and newline characters.
def json_encode(data):
if isinstance(data, bool):
if data:
return "true"
else:
return "false"
elif isinstance(data, (int, float)):
return str(data)
elif isinstance(data, str):
return '"' + escape_string(data) + '"'
elif isinstance(data, list):
return "[" + ", ".join(json_encode(d) for d in data) +
"]"
else:
raise TypeError("%s is not JSON serializable" %
repr(data))

def escape_string(s):
"""Escapes double-quote, tab and new line characters in a
string."""
s = s.replace('"', '\\"')
s = s.replace("\t", "\\t")
s = s.replace("\n", "\\n")
return s

This handles booleans, integers, strings, floats and lists, but doesn’t
handle dic!onaries yet. That is le$ an exercise to the readers.

If you no!ce the block of code that is handling lists, we are calling
json_encode recursively for each element of the list, that is required
because each element can be of any type, even a list or a dic!onary.

Problem 6: Complete the above implementa!on of json_encode by


handling the case of dic!onaries.

Problem 7: Implement a program dirtree.py that takes a directory as


argument and prints all the files in that directory recursively as a tree.

Hint: Use os.listdir and os.path.isdir fun!ons.


$ python dirtree.py foo/
foo/
|-- a.txt
|-- b.txt
|-- bar/
| |-- p.txt
| `-- q.txt
`-- c.txt

Problem 8: Write a func!on count_change to count the number of


ways to change any given amount. Available coins are also passed as
argument to the func!on.

>>> count_change(10, [1, 5])


3
>>> count_change(10, [1, 2])
6
>>> count_change(100, [1, 5, 10, 25, 50])
292

Problem 9: Write a func!on permute to compute all possible


permuta!ons of elements of a given list.

>>> permute([1, 2, 3])


[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2,
1]]

6.2. Higher Order Functions & Decorators

In Python, func!ons are first-class objects. They can be passed as


arguments to other func!ons and a new func!ons can be returned
from a func!on call.

6.2.1. Example: Tracing Function Calls

For example, consider the following fib func!on.


def fib(n):
if n is 0 or n is 1:
return 1
else:
return fib(n-1) + fib(n-2)

Suppose we want to trace all the calls to the fib func!on. We can
write a higher order func!on to return a new func!on, which prints
whenever fib func!on is called.

def trace(f):
def g(x):
print(f.__name__, x)
value = f(x)
print('return', repr(value))
return value
return g

fib = trace(fib)
print(fib(3))

This produces the following output.

fib 3
fib 2
fib 1
return 1
fib 0
return 1
return 2
fib 1
return 1
return 3
3

No!ced that the trick here is at fib = trace(fib) . We have replaced


the func!on fib with a new func!on, so whenever that func!on is
called recursively, it is the our new func!on, which prints the trace
before calling the orginal func!on.
To make the output more readable, let us indent the func!on calls.

def trace(f):
f.indent = 0
def g(x):
print('| ' * f.indent + '|--', f.__name__, x)
f.indent += 1
value = f(x)
print('| ' * f.indent + '|--', 'return', repr(value))
f.indent -= 1
return value
return g

fib = trace(fib)
print(fib(4))

This produces the following output.

$ python fib.py
|-- fib 4
| |-- fib 3
| | |-- fib 2
| | | |-- fib 1
| | | | |-- return 1
| | | |-- fib 0
| | | | |-- return 1
| | | |-- return 2
| | |-- fib 1
| | | |-- return 1
| | |-- return 3
| |-- fib 2
| | |-- fib 1
| | | |-- return 1
| | |-- fib 0
| | | |-- return 1
| | |-- return 2
| |-- return 5
5

This pa"ern is so useful that python has special syntax for specifying
this concisely.
@trace
def fib(n):
...

It is equivalant of adding fib = trace(fib) a$er the func!on


defini!on.

6.2.2. Example: Memoize

In the above example, it is clear that number of func!on calls are


growing exponen!ally with the size of input and there is lot of
redundant computa!on that is done.

Suppose we want to get rid of the redundant computa!on by caching


the result of fib when it is called for the first !me and reuse it when
it is needed next !me. Doing this is very popular in func!onal
programming world and it is called memoize .

def memoize(f):
cache = {}
def g(x):
if x not in cache:
cache[x] = f(x)
return cache[x]
return g

fib = trace(fib)
fib = memoize(fib)
print(fib(4))

If you no!ce, a$er memoize , growth of fib has become linear.


|-- fib 4
| |-- fib 3
| | |-- fib 2
| | | |-- fib 1
| | | | |-- return 1
| | | |-- fib 0
| | | | |-- return 1
| | | |-- return 2
| | |-- return 3
| |-- return 5
5

Problem 10: Write a func!on profile , which takes a func!on as


argument and returns a new func!on, which behaves exactly similar to
the given func!on, except that it prints the !me consumed in
execu!ng it.

>>> fib = profile(fib)


>>> fib(20)
time taken: 0.1 sec
10946

Problem 11: Write a func!on vectorize which takes a func!on f

and return a new func!on, which takes a list as argument and calls f

for every element and returns the result as a list.

>>> def square(x): return x * x


...
>>> f = vectorize(square)
>>> f([1, 2, 3])
[1, 4, 9]
>>> g = vectorize(len)
>>> g(["hello", "world"])
[5, 5]
>>> g([[1, 2], [2, 3, 4]])
[2, 3]

6.2.3. Example: unixcommand decorator


Many unix commands have a typical pa"ern. They accept mul!ple
filenames as arguments, does some processing and prints the lines
back. Some examples of such commands are cat and grep .

def unixcommand(f):
def g(filenames):
printlines(out for line in readlines(filenames)
for out in f(line))
return g

Lets see how to use it.

@unixcommand
def cat(line):
yield line

@unixcommand
def lowercase(line):
yield line.lower()

6.3. exec & eval

Python privides the whole interpreter as a built-in func!on. You can


pass a string and ask it is execute that piece of code at run !me.

For example:

>>> exec("x = 1")


>>> x
1

By default exec works in the current environment, so it updated the


globals in the above example. It is also possible to specify an
environment to exec .
>>> env = {'a' : 42}
>>> exec('x = a+1', env)
>>> print(env['x'])
43

It is also possible to create func!ons or classes dynamically using


exec , though it is usually not a good idea.

>>> code = 'def add_%d(x): return x + %d'


>>> for i in range(1, 5):
... exec(code % (i, i))
...
>>> add_1(3)
4
>>> add_3(3)
6

eval is like exec but it takes an expression and returns its value.

>>> eval("2+3")
5
>>> a = 2
>>> eval("a * a")
4
>>> env = {'x' : 42}
>>> eval('x+1', env)
43

You might also like