Paython
Paython
Paython
com/python-practice-
book/getting-started.html
1. Getting Started
1.1. Running Python Interpreter
Python comes with an interactive interpreter. When you type python in your shell
or command prompt, the python interpreter becomes active 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 .
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.
>>> 1 + 2
3
Python is pretty good at handling very large numbers as well. For example, let us
try to compute 2 raises to the power of 1000.
>>> 2 ** 1000
107150860718626732094842504906000181056140481170553360744375038837035105112493612249319
837881569585812759467291755314682518714528569231404359845775746985748039345677748242309
854210746050623711418779541821530464749835819412673987675591655439460770629145711964776
86542167660429831652624386837205668069376
That is a pretty big numbers, isn’t it? Can you count how many digits it has?
>>> "hello" * 3
'hellohellohello'
>>> len('helloworld')
10
Python supports multi-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.
>>> point = 2, 3
>>> point
(2, 3)
It is also possible to assign a tuple multiple values at once:
>>> 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 practice. Make sure you go through all
the examples and the practice problems in the subsequent sections.
1.4. Variables
You’ve already seen variables in the previous section. 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 notice the difference between variables and strings. Often new
programmers get tricked by this. Can you spot any error in the following
example?
1.5. Functions
Python has many built-in functions. The print is the most commonly used built-in
function.
>>> print('hello')
hello
>>> print('hello', 1, 2, 3)
hello 1 2 3
We’ve also see the len function in the previous sections. The len function
computes the length of a string, list or other collections.
>>> len("hello")
5
>>> len(['a', 'b', 'c'])
3
One important thing about Python is that it doesn’t allow operations on
incompatible 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 function 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
>>> 12345
12345
>>> 2 ** 100
1267650600228229401496703205376
>>> 2 ** 1000
107150860718626732094842504906000181056140481170553360744375038837035105112493612249319
837881569585812759467291755314682518714528569231404359845775746985748039345677748242309
854210746050623711418779541821530464749835819412673987675591655439460770629145711964776
86542167660429831652624386837205668069376
We can combile the previously mentioned built-in functions to solve this.
>>> len(str(12345))
5
>>> len(str(2 ** 100))
31
>>> len(str(2 * 1000))
302
The ... is the secondary prompt, which the Python interpreter uses to denote
that it is expecting some more input.
>>> f = square
>>> f(4)
16
x = 0
y = 0
def incr(x):
y = x + 1
return y
incr(5)
print x, y
Variables assigned in a function, including the arguments are called the local
variables to the function. The variables defined in the top-level are called global
variables.
Changing the values of x and y inside the function incr won’t effect the values of
global x and y .
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 multiplications 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
Functions can be called with keyword arguments.
The lambda operator becomes handy when writing small functions to be passed
as arguments etc. We’ll see more of it as we get into solving more serious
problems.
>>> min(2, 3)
2
>>> max(3, 4)
4
The built-in function len computes length of a string.
>>> len("helloworld")
10
The built-in function int converts string to ingeter and built-in
function str converts integers and other type of objects to strings.
>>> int("50")
50
>>> str(123)
"123"
Problem 7: Write a function count_digits to find number of digits in the given
number.
>>> count_digits(5)
1
>>> count_digits(12345)
5
Methods are special kind of functions that work on an object.
>>> x = "hello"
>>> print x.upper()
HELLO
As already mentioned, methods are also functions. They can be assigned to
other variables can be called separately.
>>> f = x.upper
>>> f()
'HELLO'
Problem 8: Write a function istrcmp to compare two strings, ignoring the case.
>>> 2 < 3
True
>>> 2 > 3
False
Here is the list of available conditional operators.
== equal to
!= not equal to
>>> x = 5
>>> 2 < x < 10
True
>>> 2 < 3 < 4 < 5 < 6
True
The conditional operators work even on strings - the ordering being the lexical
order.
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 written as a separate indented block of code,
which is often the case when there is more than one statement to be executed.
>>> if x % 2 == 0:
... print('even')
...
even
>>>
The if statement can have optional 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 optional elif clauses when there are more conditions
to be checked. The elif keyword is short for else if , and is useful to avoid
excessive indentation.
>>> 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 little
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.
>>> x = [1, 2, 3]
And here is a list of strings.
>>> 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.
1.9. Modules
Modules are libraries in Python. Python ships with many standard library
modules.
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
convention, 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 add.py 3 5
8
$ python add.py 2 9
11
2. Working with Data
2.1. Lists
We’ve already seen quick introduction 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 function range can be used to create a sequence of consequetive
integers.
The range function returns a specical range object that behaves like a list. To get
a real list from it, you can use the list function.
>>> x = range(1, 4)
>>> x
range(1, 4)
>>> x[0]
1
>>> len(x)
3
The built-in function 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
Negative 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 negative 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 omitted first index defaults to zero, an
omitted 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 optional 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 function, 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)
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 i in range(10):
print(i, i*i, i*i*i)
The built-in function zip takes two lists and returns list of pairs.
>>> factorial(4)
24
Problem 6: Write a function reverse to reverse a list. Can you do this without
using list slicing?
>>> 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 function 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.
Problem 13: Write a function lensort to sort a list of strings based on length.
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 optional.
>>> a = 1, 2, 3
>>> a[0]
1
The built-in function 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 additional comma.
>>> a = (1)
>> a
1
>>> b = (1,)
>>> b
(1,)
>>> b[0]
1
2.3. Sets
Sets are unordered collection of unique elements.
>>> x = {3, 1, 2, 1}
set([1, 2, 3])
New elements can be added to a set using the add method.
>>> 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.
>>> a = 'hello'
>>> b = 'python'
>>> "%s %s" % (a, b)
'hello python'
>>> 'Chapter %d: %s' % (2, 'Data Structures')
'Chapter 2: Data Structures'
Problem 16: Write a function extsort to sort a list of files based on extension.
Unix does not distinguish 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 respectively.
>>> 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')
>>> 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.
def charcount(filename):
return len(open(filename).read())
Number of words in a file can be found by splitting 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.
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.
>>> 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 multiple lists using the built-in function 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 multiple for clauses in single list comprehension.
>>> 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 implementation for zip function using list
comprehensions.
>>> triplets(5)
[(1, 1, 2), (1, 2, 3), (1, 3, 4), (2, 2, 4)]
Problem 28: Write a function enumerate that takes a list and returns a list of tuples
containing (index,item) for each item in the list.
>>> 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 function 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 implementation 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 function mutate to compute all words generated by a single
mutation on a given word. A mutation is defined as inserting a character, deleting
a character, replacing a character, or swapping 2 consecutive characters in a
string. For simplicity consider only letters from a to z .
2.7. Dictionaries
Dictionaries are like lists, but they can be indexed with non integer keys also.
Unlike lists, dictionaries are not ordered.
>>> 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 dictionary.
Lets first write a function to count frequency of words, given a list of words.
def word_frequency(words):
"""Returns frequency of each word given a list of words.
def read_words(filename):
return open(filename).read().split()
We can combine these two functions 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.
Python stores the variables we use as a dictionary. The globals() function 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 function locals which gives all the local
variables in a function.
3. Modules
Modules are reusable libraries of code in Python. Python comes with many
standard library modules.
$ 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 function.
>>> help('time')
Help on module time:
NAME
time - This module provides various functions to manipulate time values.
...
Writing our own modules is very simple.
def square(x):
return x * x
def cube(x):
return x * x * x
Now open Python interterter:
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 doumentation nicely formatted.
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 documentation as a special field
called __doc__.
>>> import os
>>> print(os.getcwd.__doc__)
getcwd() -> path
3.1.1. os module
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.
>>> 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.
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 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:
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
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 activated.
Now all the packages installed will be limited to this virtualenv. Lets try to install a
third-party package.
The tablib library is a small little library to work with tabular data and write csv
and Excel files.
# 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)
sheet1 = tablib.Dataset()
sheet1.append(["A1", 1])
sheet1.append(["A2", 2])
sheet2 = tablib.Dataset()
sheet2.append(["B1", 1])
sheet2.append(["B2", 2])
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 getting complicated if want to model multiple accounts.
We can solve the problem by making the state local, probably by using a
dictionary to store the state.
def make_account():
return {'balance': 0}
>>> a = make_account()
>>> b = make_account()
>>> deposit(a, 100)
100
>>> deposit(b, 50)
50
>>> withdraw(b, 10)
40
>>> withdraw(a, 10)
90
>>> 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 little more sophisticated 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
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 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
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
>>> 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.
We can add, subtract, multiply, divide, and test equality by using the following
relations:
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 __str__(self):
return "%s/%s" % (self.n, self.d)
__repr__ = __str__
>>> 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 exception in case errors. We can write programs to handle such
errors. We too can raise exceptions when an error case in encountered.
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.
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 optional else clause, which is executed only if no
exception 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 optional else clause with a try statement, which is executed
irrespective of whether or not exception 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()
Exception is raised using the raised keyword.
class ParseError(Exception):
pass
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.1. Iterators
We use for statement for looping over a list.
The built-in function iter takes an iterable object and returns an iterator.
Iterators are implemented as classes. Here is an iterator that works like built-
in range function.
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 function 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.
>>> 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 functions 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. Notice 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
iteration.
>>> 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 direction. ::
5.2. Generators
Generators simplifies creation of iterators. A generator is a function that produces
a sequence of results instead of a single value.
def yrange(n):
i = 0
while i < n:
yield i
i += 1
Each time the yield statement is executed the function 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 function that
generates and what it generates. In this chapter, I’ll use the word “generator” to
mean the genearted object and “generator function” to mean the function that
generates it.
The following example demonstrates the interplay between yield and call
to __next__ method on generator object.
def integers():
"""Infinite sequence of integers."""
i = 1
while True:
yield i
i = i + 1
def squares():
for i in integers():
yield i * i
It is easy to solve this problem if we know till what value of z to test for. But we
want to find first n pythogorian triplets.
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.
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 particular substring,
like grep command in unix.
def readfiles(filenames):
for f in filenames:
for line in open(f):
yield line
def printlines(lines):
for line in lines:
print(line, end="")
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 function findfiles that recursively descends the directory tree
for the specified directory and generates paths of all the files in the tree.
Problem 5: Write a function to compute the total number of lines of code in all
python files in the specified directory recursively.
5.4. Itertools
he itertools module in the standard library provides lot of intersting tools to work
with iterators.
Lets look at some of the interesting functions.
>>> it = iter(range(5))
>>> x, it1 = peep(it)
>>> print(x, list(it1))
0 [0, 1, 2, 3, 4]
Problem 9: The built-in function enumerate takes an iteratable and returns an
iterator over pairs (index, value) for each value in the source.
>>> exp(2, 3)
8
>>> exp(3, 2)
9
"""
if n == 0:
return 1
else:
return x * exp(x, n-1)
Lets look at the execution pattern.
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 function is proportional to size of the problem,
which is n here.
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 function product to multiply 2 numbers recursively
using + and - operators only.
6.1.2. Example: Flatten a list
for x in a:
if isinstance(x, list):
flatten_list(x, result)
else:
result.append(x)
return result
Problem 2: Write a function flatten_dict to flatten a nested dictionary by joining
the keys with . character.
{
"name": "Advanced Python Training",
"date": "October 13, 2012",
"completed": false,
"instructor": {
"name": "Anand Chitipothu",
"website": "http://anandology.com/"
},
"participants": [
{
"name": "Participant 1",
"email": "email1@example.com"
},
{
"name": "Participant 2",
"email": "email2@example.com"
}
]
}
It looks very much like Python dictionaries and lists. There are some differences
though. Strings are always enclosed in double quotes, booleans are represented
as true and false .
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
dictionaries yet. That is left an exercise to the readers.
Problem 6: Complete the above implementation of json_encode by handling the
case of dictionaries.
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 function. We can write a higher
order function to return a new function, which prints whenever fib function 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
Noticed that the trick here is at fib = trace(fib) . We have replaced the
function fib with a new function, so whenever that function is called recursively, it
is the our new function, which prints the trace before calling the orginal function.
To make the output more readable, let us indent the function 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 pattern is so useful that python has special syntax for specifying this
concisely.
@trace
def fib(n):
...
It is equivalant of adding fib = trace(fib) after the function definition.
In the above example, it is clear that number of function calls are growing
exponentially with the size of input and there is lot of redundant computation that
is done.
Suppose we want to get rid of the redundant computation by caching the result
of fib when it is called for the first time and reuse it when it is needed next time.
Doing this is very popular in functional 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 notice, after 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 function profile , which takes a function as argument and
returns a new function, which behaves exactly similar to the given function,
except that it prints the time consumed in executing it.
Many unix commands have a typical pattern. They accept multiple 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()
For example:
>>> eval("2+3")
5
>>> a = 2
>>> eval("a * a")
4
>>> env = {'x' : 42}
>>> eval('x+1', env)
43
Previous