Python Practice Book 1.0
Python Practice Book 1.0
ce Book
Table of Contents
1. Ge"ng Started
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
4.1. State
4.2. Classes and Objects
4.3. Inheritance
4.4. Special Class Methods
4.5. Errors and Excep!ons
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
1. Getting Started
$ 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 .
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.
>>> 1 + 2
3
>>> 2 ** 1000
107150860718626732094842504906000181056140481170553360744375038837035105112493
That is a pre#y big numbers, isn’t it? Can you count how many digits it
has?
>>> "hello" * 3
'hellohellohello'
>>> len('helloworld')
10
>>> print("a\nb\nc")
a
b
c
Python has lists. Lists are one of the most useful data types Python.
Python has another datatype called tuple for represen"ng fixed width
records. Tuples behave just like lists, but they are immutable.
>>> 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.
>>> 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
>>> x = 5
>>> x
5
>>> x = 'hello'
>>> x
'hello'
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
>>> len("hello")
5
>>> len(['a', 'b', 'c'])
3
>>> 5 + "2"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
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'
>>> 12345
12345
>>> 2 ** 100
1267650600228229401496703205376
>>> 2 ** 1000
107150860718626732094842504906000181056140481170553360744375038837035105112493
>>> len(str(12345))
5
>>> len(str(2 ** 100))
31
>>> len(str(2 * 1000))
302
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.
The ... is the secondary prompt, which the Python interpreter uses
to denote that it is expec"ng some more input.
Func"ons are just like other values, they can assigned, passed as
arguments to other func"ons etc.
>>> f = square
>>> f(4)
16
x = 0
y = 0
def incr(x):
y = x + 1
return y
incr(5)
print(x, 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.
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))
x = 1
def f():
return x
print(x)
print(f())
x = 1
def f():
x = 2
return x
print(x)
print(f())
print(x)
x = 1
def f():
y = x
x = 2
return x + y
print(x)
print(f())
print(x)
>>> min(2, 3)
2
>>> max(3, 4)
4
>>> len("helloworld")
10
>>> int("50")
50
>>> str(123)
"123"
>>> count_digits(5)
1
>>> count_digits(12345)
5
Methods are special kind of func"ons that work on an object.
>>> x = "hello"
>>> print(x.upper())
HELLO
>>> f = x.upper
>>> f()
'HELLO'
>>> 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
>>> x = 5
>>> 2 < x < 10
True
>>> 2 < 3 < 4 < 5 < 6
True
x = 4
y = 5
p = x < y or x < z
print(p)
>>> x = 42
>>> if x % 2 == 0: print('even')
even
>>>
>>> if x % 2 == 0:
... print('even')
...
even
>>>
>>> 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
>>> 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
>>> x = [1, 2, 3]
>>> x = [1, 2, 3]
>>> len(x)
3
>>> x = [1, 2, 3]
>>> x[1]
2
>>> x[1] = 4
>>> x[1]
4
1.9. Modules
Lets look at the following program echo.py that prints the first
argument passed to it.
import sys
print(sys.argv[1])
There are many more interes"ng modules in the standard library. We’ll
learn more about them in the coming chapters.
$ python add.py 3 5
8
$ python add.py 2 9
11
! » 2. Working with Data
2.1. Lists
>>> [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 = [1, 2]
>>> b = [1.5, 2, a]
>>> b
[1.5, 2, [1, 2]]
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
>>> a = [1, 2, 3]
>>> b = [4, 5]
>>> a + b
[1, 2, 3, 4, 5]
>>> b * 3
[4, 5, 4, 5, 4, 5]
>>> x = [1, 2]
>>> x[0]
1
>>> x[1]
2
>>> x = [1, 2, 3, 4]
>>> x[-1]
4
>>> x [-2]
3
>>> x = [1, 2, 3, 4]
>>> x[0:2]
[1, 2]
>>> x[1:4]
[2, 3, 4]
>>> x[0:-1]
[1, 2, 3]
>>> x = range(10)
>>> x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x[0:6:2]
[0, 2, 4]
>>> x[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> x = [1, 2, 3, 4]
>>> x[1] = 5
>>> x
[1, 5, 3, 4]
>>> 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]
x = [0, 1, [2]]
x[2][0] = 3
print(x)
x[2].append(4)
print(x)
x[2] = 2
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.
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.
Problem 11: Write a func!on dups to find all duplicates in the list.
Problem 12: Write a func!on group(list, size) that take a list and splits
into smaller lists of given size.
The sort method works even when the list has different types of
objects and even lists.
This sorts all the elements of the list based on the value of second
element of each entry.
2.2. Tuples
>>> a = (1, 2, 3)
>>> a[0]
1
>>> a = 1, 2, 3
>>> a[0]
1
>>> 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
>>> x = {3, 1, 2, 1}
set([1, 2, 3])
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
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
>>> 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 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.
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.
>>> 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()
''
>>> f = open('foo.txt')
>>> f.writelines(['a\n', 'b\n', 'c\n'])
>>> f.close()
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())
def wordcount(filename):
return len(open(filename).read().split())
def linecount(filename):
return len(open(filename).readlines())
$ 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.
and tail commands take a file as argument and prints its first and
last 10 lines of the file respec!vely.
Problem 21: Write a program wrap.py that takes filename and width as
aruguments and wraps the lines longer than width.
>>> 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]
>>> 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]
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.
>>> 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.
>>> a = array(2, 3)
>>> a
[[None, None, None], [None, None, None]]
>>> a[0][0] = 5
[[5, None, None], [None, None, None]]
>>> 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']]
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.
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)]
>>> 'x' in a
True
>>> 'p' in a
False
>>> a.has_key('x')
True
>>> a.has_key('p')
False
def word_frequency(words):
"""Returns frequency of each word given a list of words.
def read_words(filename):
return open(filename).read().split()
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.
>>> 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.
3. Modules
Modules are reusable libraries of code in Python. Python comes with
many standard library modules.
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.
Here were imported just the asc!me func!on from the !me module.
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
...
>>> help('time')
Help on module time:
NAME
time - This module provides various functions to manipulate
time values.
...
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
def square(x):
"""Computes square of a number."""
return x * x
def cube(x):
"""Computes cube of a number."""
return x * x
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.
>>> import os
>>> print(os.getcwd.__doc__)
getcwd() -> path
Python comes with many standard library modules. Lets look at some
of the most commonly used ones.
3.1.1. os module
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.
>>> response.header['Content-Type']
'text/html'
$ python wget.py
http://docs.python.org/tutorial/interpreter.html
saving http://docs.python.org/tutorial/interpreter.html as
interpreter.html.
3.1.3. re 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)
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.
DESCRIPTION
FUNCTIONS
getcwd()
...
Hints:
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.
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.
$ virtualenv testenv
On UNIX/Mac OS X:
$ source testenv/bin/activate
On Windows:
> testenv\Scripts\activate
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 li"le 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)
sheet2 = tablib.Dataset()
sheet2.append(["B1", 1])
sheet2.append(["B2", 2])
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.
4.1. State
balance = 0
def deposit(amount):
global balance
balance += amount
return balance
def withdraw(amount):
global balance
balance -= amount
return balance
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}
>>> 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 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 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())
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
We can add, subtract, mul!ply, divide, and test equality by using the
following rela!ons:
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
>>> "foo" + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects
>>> 2/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
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.
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)
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()
All the excep!ons are extended from the built-in Excep!on class.
class ParseError(Excep!on):
pass
try:
print("a")
raise Exception("doom")
except:
print("b")
else:
print("c")
finally:
print("d")
def f():
try:
print("a")
return
except:
print("b")
else:
print("c")
finally:
print("d")
f()
! » 5. Iterators & Generators
5.1. Iterators
So there are many types of objects which can be used with a for loop.
These are called iterable objects.
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()
>>> 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()
>>> 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]
5.2. Generators
def yrange(n):
i = 0
while i < n:
yield i
i += 1
>>> 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.
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.
def squares():
for i in integers():
yield i * i
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 readfiles(filenames):
for f in filenames:
for line in open(f):
yield line
def printlines(lines):
for line in lines:
print(line, end="")
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.
5.4. Itertools
>>> it = iter(range(5))
>>> x, it1 = peep(it)
>>> print(x, list(it1))
0 [0, 1, 2, 3, 4]
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
>>> exp(2, 3)
8
>>> exp(3, 2)
9
"""
if n == 0:
return 1
else:
return x * exp(x, n-1)
for x in a:
if isinstance(x, list):
flatten_list(x, result)
else:
result.append(x)
return result
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 .
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.
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))
fib 3
fib 2
fib 1
return 1
fib 0
return 1
return 2
fib 1
return 1
return 3
3
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))
$ 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):
...
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))
and return a new func!on, which takes a list as argument and calls f
def unixcommand(f):
def g(filenames):
printlines(out for line in readlines(filenames)
for out in f(line))
return g
@unixcommand
def cat(line):
yield line
@unixcommand
def lowercase(line):
yield line.lower()
For example:
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