Python Training at Intuit Bangalore - Day 2

Feb 20-22, 2017
Anand Chitipothu

These notes are available online at http://bit.ly/intuit17

© Pipal Academy LLP

Home | Day 1 | Day 2 | Day 3

The if Statement

In [1]:
n = 35 
if n % 2 == 0:
    print("even")
else:
    print("odd")
odd
In [2]:
def check_even(n):
    if n % 2 == 0:
        print("even")
    else:
        print("odd")    
In [3]:
check_even(4)
even
In [4]:
check_even(5)
odd

Checking multiple conditions can be done using elif statements.

In [5]:
def check_number(n):
    if n < 10:
        print(n, "is a single digit number")
    elif n < 100:
        print(n, "is a two digit number")
    else:
        print(n, "is a big number")
In [6]:
check_number(3)
3 is a single digit number
In [7]:
check_number(54)
54 is a two digit number
In [8]:
check_number(267)
267 is a big number

Problem: Write a function minimum to compute the minimum of two numbers, without using the built-in function min. Please note that the function should return a value, not print.

>>> minimum(3, 7)
3
>>> minimum(33, 7)
7
>>> 1 + minimum(3, 7)
4
>>> 1 + minimum(3, 3)
4    

Problem: Write a function minimum3 to compute minimum of three numbers. Can you do this by using the minimum function defined above?

>>> minimum3(2, 3, 4)
2
>>> minimum3(12, 3, 4)
3    
>>> minimum3(12, 13, 4)
4        
In [11]:
def minimum(x, y):
    if x < y:
        return x
    else:
        return y

print(minimum(3, 7))
print(minimum(33, 7))
print(1+minimum(3, 7))
print(1+minimum(3, 3))
3
7
4
4
In [12]:
def minimum3(x, y, z):
    a = minimum(x, y)
    if a < z:
        return a
    else:
        return z

print(minimum3(2, 3, 4))
print(minimum3(12, 3, 4))
print(minimum3(12, 13, 4))
2
3
4
In [13]:
def minimum3(x, y, z):
    a = minimum(x, y)
    return minimum(a, z)

print(minimum3(2, 3, 4))
print(minimum3(12, 3, 4))
print(minimum3(12, 13, 4))
2
3
4
In [14]:
def minimum3(x, y, z):
    return minimum(minimum(x, y), z)

print(minimum3(2, 3, 4))
print(minimum3(12, 3, 4))
print(minimum3(12, 13, 4))
2
3
4

Lists

In [15]:
x = ["a", "b", "c", "d"]
In [16]:
len(x)
Out[16]:
4
In [17]:
x[0]
Out[17]:
'a'
In [18]:
x[1]
Out[18]:
'b'

For Loop

In [19]:
x = ["a", "b", "c", "d"]
for value in x:
    print(value)
a
b
c
d
In [21]:
names = ["Alice", "Bob", "Charlie", "Dave"]
for name in names:
    print(name)
Alice
Bob
Charlie
Dave
In [22]:
for name in names:
    print("Hello", name)
Hello Alice
Hello Bob
Hello Charlie
Hello Dave

How to print all words in a sentence?

In [23]:
sentence = "when in doubt, use brute force"
In [24]:
for word in sentence.split():
    print(word)
when
in
doubt,
use
brute
force

Problem: Write a program ls.py that takes path to a directory as command-line argument and prints all the files in that directory. The output should contain one filename per line.

$ python ls.py .
Makefile
day1.ipynb
day1.html
hello.py

Hint: see os.listdir

Python has a built-in function range to iterate over sequence of numbers.

In [25]:
for i in range(5):
    print(i)
0
1
2
3
4

range(5) gives values from 0 to 4.

In [26]:
for i in range(2, 5):
    print(i)
2
3
4
In [27]:
# say hello n times
def say_hello(name, n):
    for i in range(n):
        print("Hello", name)
In [29]:
say_hello("Everyone", 4)
Hello Everyone
Hello Everyone
Hello Everyone
Hello Everyone

Example: Counting sum of numbers

Python has a built-in function sum to compute sum of a list of numbers.

In [30]:
sum([1, 2, 3, 4, 5])
Out[30]:
15
In [31]:
sum(range(10))
Out[31]:
45
In [32]:
sum(range(1000000))
Out[32]:
499999500000

Let us try to implement our own sum function.

In [37]:
def my_sum(numbers):
    result = 0
    print("result =", result)
    for n in numbers:
        result = result + n
        print("n =", n, "result =", result)
    return result

print(my_sum([10, 20, 30, 40, 50]))
# print(my_sum(range(10)))
# print(my_sum(range(1000000)))
result = 0
n = 10 result = 10
n = 20 result = 30
n = 30 result = 60
n = 40 result = 100
n = 50 result = 150
150

Problem: Write a function product to compute product of given list of numbers.

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

Problem: Write a function factorial that takes a number as argument and computes its factorial. Can you use the above implementation of product in computing it?

>>> factorial(4)
24
In [38]:
def product(numbers):
    result = 1
    for n in numbers:
        result = result * n
    return result
In [40]:
print(product([10, 20, 30, 40]))
240000
In [41]:
def factorial(n):
    numbers = range(1, n+1)
    return product(numbers)
In [42]:
factorial(4)
Out[42]:
24
In [44]:
factorial(0)
Out[44]:
1
In [45]:
factorial(5)
Out[45]:
120
In [46]:
1*2*3*4*5
Out[46]:
120

Modifying and Growing Lists

In [47]:
x = ["a", "b", "c", "d"]
In [48]:
x
Out[48]:
['a', 'b', 'c', 'd']
In [49]:
x[1]
Out[49]:
'b'
In [50]:
x[1] = 'bb'
In [51]:
x
Out[51]:
['a', 'bb', 'c', 'd']
In [52]:
x.append('e')
In [53]:
x
Out[53]:
['a', 'bb', 'c', 'd', 'e']

Notice that the append method doesn't return anything, but modifies the list in-place.

Example: squares

Let us write a function squares to compute squares of all numbers in a list.

In [55]:
def squares(numbers):
    result = []
    for n in numbers:
        result.append(n*n)
    return result
In [60]:
print(squares([10, 20, 30, 40]))
[100, 400, 900, 1600]
In [61]:
print(squares(range(10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
In [59]:
# sum of squares of all numbers below one million
sum(squares(range(1000000)))
Out[59]:
333332833333500000

Problem: Write a function evens that takes a list of numbers as argument and returns a new list containing only the even numbers out of them.

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

List Comprehensions

In [62]:
x = [1, 2, 3, 4, 5, 6]
In [63]:
[a*a for a in x]
Out[63]:
[1, 4, 9, 16, 25, 36]
In [64]:
[a*a for a in x if a%2 == 0]
Out[64]:
[4, 16, 36]
In [65]:
names = ["a", "b", "c", "d"]
In [66]:
uppernames = [name.upper() for name in names]
In [67]:
uppernames
Out[67]:
['A', 'B', 'C', 'D']

Problem: Write a function list_pyfiles that takes path to a directory as argument and returns all the python files in that directory.

>>> list_pyfiles(".")
['echo.py', 'square.py', ...]

List comprehensions are used to transform one list to another.

They are usually in the following form:

[expr for var in alist]
[expr for var in alist if some_cond]
In [69]:
import os

def list_pyfiles(path):
    return [f for f in os.listdir(path) if f.endswith(".py")]
In [70]:
list_pyfiles(".")
Out[70]:
['args.py', 'echo.py', 'hello.py', 'square.py']

How to find total size of all python files in the current directory?

In [72]:
[f for f in os.listdir(".") if f.endswith(".py")]
Out[72]:
['args.py', 'echo.py', 'hello.py', 'square.py']
In [73]:
[os.path.getsize(f) for f in os.listdir(".") if f.endswith(".py")]
Out[73]:
[26, 30, 20, 59]
In [74]:
sum([os.path.getsize(f) for f in os.listdir(".") if f.endswith(".py")])
Out[74]:
135

Iterations Patterns

Iterating over a list

In [75]:
x = ['a', 'b', 'c', 'd']
for a in x:
    print(a)
a
b
c
d
In [76]:
x = ['a', 'b', 'c', 'd']
for a in x:
    print(a, a.upper())
a A
b B
c C
d D
In [77]:
[a.upper() for a in x]
Out[77]:
['A', 'B', 'C', 'D']

Iterating over a sequence of values

In [78]:
for i in range(5):
    print(i)
0
1
2
3
4
In [80]:
for i in range(2, 5):
    print(i, i*i)
2 4
3 9
4 16
In [81]:
[i*i for i in range(10)]
Out[81]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Iterating over two lists together

In [82]:
names = ["a", "b", "c", "d"]
scores = [10, 20, 30, 40]
In [83]:
zip(names, scores)
Out[83]:
<zip at 0x1052ef788>
In [84]:
list(zip(names, scores))
Out[84]:
[('a', 10), ('b', 20), ('c', 30), ('d', 40)]
In [85]:
for name, score in zip(names, scores):
    print(name, score)
a 10
b 20
c 30
d 40
In [86]:
for pair in zip(names, scores):
    print(pair)
('a', 10)
('b', 20)
('c', 30)
('d', 40)
In [88]:
# since pair is a tuple of two elements, 
# we can assign that to two different variables.
for pair in zip(names, scores):
    name, score = pair
    print(name, score)
a 10
b 20
c 30
d 40
In [89]:
# When we use two variables in the for loop, thats what is done implicitly.
for name, score in zip(names, scores):
    print(name, score)
a 10
b 20
c 30
d 40

Problem: Write a function vector_add to add two vectors.

>>> vector_add([1, 2, 3, 4], [10, 20, 30, 40])
[11, 22, 33, 44]
In [90]:
def vector_add(A, B):
    return [a+b for a, b in zip(A, B)]
In [91]:
vector_add([1, 2, 3, 4], [10, 20, 30, 40])
Out[91]:
[11, 22, 33, 44]

Iterating over the index and the value together

In [92]:
names = ["a", "b", "c", "d"]
In [93]:
for i, name in enumerate(names):
    print(i, name)
0 a
1 b
2 c
3 d
In [94]:
list(enumerate(names))
Out[94]:
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]

Let us look at a simple example.

In [95]:
chapters = ["Getting started", "Lists", "Working with Files"]
In [96]:
for i, title in enumerate(chapters):
    print("chapter", i+1, ":", title)
chapter 1 : Getting started
chapter 2 : Lists
chapter 3 : Working with Files
In [97]:
for i, title in enumerate(chapters, start=1):
    print("chapter", i, ":", title)
chapter 1 : Getting started
chapter 2 : Lists
chapter 3 : Working with Files

List Indexing

In [98]:
x = ["a", "b", "c", "d"]
In [99]:
x[0]
Out[99]:
'a'
In [100]:
x[1]
Out[100]:
'b'

How to get the last element of x?

In [101]:
x[len(x)-1]
Out[101]:
'd'
In [104]:
x[-1] # last element
Out[104]:
'd'
In [105]:
x[-2] # second last element
Out[105]:
'c'

Let us try an example.

In [106]:
def get_last_word(sentence):
    return sentence.split()[-1]
In [107]:
get_last_word("one two three")
Out[107]:
'three'

Problem: Write a function getext to find the extension of given filename.

>>> getext("a.py")
'py'
>>> getext("a.tar.gz")
'gz'    

Assume that the filename will always have an extension.

List Slicing

In [108]:
x = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
In [109]:
x[0:2]
Out[109]:
['a', 'b']
In [110]:
x[:2] # upto index 2 (2 is not included)
Out[110]:
['a', 'b']
In [111]:
x[2:] # index 2 onwards
Out[111]:
['c', 'd', 'e', 'f', 'g', 'h']
In [112]:
x[2:6] # from index 2 to index 6
Out[112]:
['c', 'd', 'e', 'f']
In [113]:
x[1:6:2] # take every second element start from index 1 to 6
Out[113]:
['b', 'd', 'f']
In [114]:
x[:] # copy of x
Out[114]:
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
In [115]:
x[::-1] # reverse of the list
Out[115]:
['h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
In [116]:
x[:-1] # all, but not the last element
Out[116]:
['a', 'b', 'c', 'd', 'e', 'f', 'g']

Example: echo2.py

Earlier we wrote a program echo.py to print the first command-line argument. Let us improve that to print all command-line arguments.

In [132]:
%%file echo2.py
import sys
#print(sys.argv)

args = sys.argv[1:] # skip the program name
#print(args)

print(" ".join(args))
Overwriting echo2.py
In [133]:
!python echo2.py hello world
hello world
In [134]:
!python echo2.py hello world hello everyone
hello world hello everyone

Problem: Write a program sum.py that takes multiple numbers as command-line arguments and prints their sum.

$ python sum.py 1 2 3
6
$ python sum.py 1 2 3 4 5
15
In [141]:
%%file sum.py
import sys
print(sys.argv)
args = sys.argv[1:]
print(args)

numbers = [int(a) for a in args]
print(numbers)
print(sum(numbers))
Overwriting sum.py
In [142]:
!python sum.py 1 2 3 
['sum.py', '1', '2', '3']
['1', '2', '3']
[1, 2, 3]
6
In [145]:
%%file sum.py
import sys
args = sys.argv[1:]
numbers = [int(a) for a in args]
print(sum(numbers))
Overwriting sum.py
In [146]:
!python sum.py 1 2 3 
6
In [147]:
%%file sum.py
import sys
print(sum([int(a) for a in sys.argv[1:]]))
Overwriting sum.py
In [149]:
!python sum.py 1 2 3 4 5
15

Writing Custom Modules

In [150]:
%%file mymodule.py
print("begin mymodule")

x = 1

def add(a, b):
    return a+b

print(add(3, 4))

print("end mymodule")
Overwriting mymodule.py
In [151]:
!python mymodule.py
begin mymodule
7
end mymodule
In [152]:
%%file a.py
print("before import")
import mymodule
print("after import")

print(mymodule.x)
print(mymodule.add(10, 20))
Overwriting a.py
In [153]:
!python a.py
before import
begin mymodule
7
end mymodule
after import
1
30

The __name__ special variable

In [154]:
%%file mymodule2.py

x = 1

def add(a, b):
    return a+b

print(add(3, 4))
print(__name__)
Writing mymodule2.py
In [155]:
!python mymodule2.py
7
__main__
In [156]:
!python -c "import mymodule2"
7
mymodule2

When the file is executed as a script, the value of __name__ is set to "__main__". When the file is imported as a module, it is set to the module name.

In [157]:
%%file mymodule3.py

x = 1

def add(a, b):
    return a+b

if __name__ == "__main__":
    # Run the following code only when this file is 
    # executed as a script. 
    # Ignore this when this file is imported as a module.
    print(add(3, 4))
Writing mymodule3.py
In [158]:
!python mymodule3.py
7
In [159]:
!python -c "import mymodule3"

Docstrings

In [160]:
%%file square.py
"""The square module.

The long description of the module.

copyright: blah blah blah
"""
import sys

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

def main():
    n = int(sys.argv[1])
    print(square(n))

if __name__ == "__main__":
    main()
Overwriting square.py
In [161]:
!python square.py 6
36
In [163]:
import square
print(square.square(8))
64
In [164]:
help(square)
Help on module square:

NAME
    square - The square module.

DESCRIPTION
    The long description of the module.
    
    copyright: blah blah blah

FUNCTIONS
    main()
    
    square(x)
        Computes square of a number.
        
        >>> square(4)
        16

FILE
    /Users/anand/trainings/2017/intuit/square.py


Problem: Write cube module.

Testing Python Programs

Note: You'll have to install third-party module py.test to do the following. Instructions will be provided at the end.

In [165]:
def square(x):
    return x*x
In [166]:
print(square(3))
9

It is hard to know if the output is correct or not. A better way would be to test for correct value.

In [167]:
if square(3) == 9:
    print("TEST PASSED")
TEST PASSED

Python has an assert statement to test for things.

In [170]:
def test_square():
    assert square(0) == 0
    assert square(3) == 9
    assert square(-3) == 90   
In [171]:
test_square()
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-171-108216126958> in <module>()
----> 1 test_square()

<ipython-input-170-bee36f7ca5fb> in test_square()
      2     assert square(0) == 0
      3     assert square(3) == 9
----> 4     assert square(-3) == 90

AssertionError: 

There are some tools to make running tests easier.

In [176]:
%%file sq.py

def square(x):
    return x*x

def sum_of_squares(x, y):
    return square(x) + square(y)

def test_square():
    assert square(0) == 0
    assert square(3) == 9
    assert square(-3) == 90
    
def test_sum_of_squares():
    assert sum_of_squares(0, 0) == 0
    assert sum_of_squares(3, 4) == 25
Overwriting sq.py
In [177]:
!py.test sq.py
============================= test session starts ==============================
platform darwin -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1
rootdir: /Users/anand/trainings/2017/intuit, inifile: 
collected 2 items 

sq.py F.

=================================== FAILURES ===================================
_________________________________ test_square __________________________________

    def test_square():
        assert square(0) == 0
        assert square(3) == 9
>       assert square(-3) == 90
E       assert 9 == 90
E        +  where 9 = square(-3)

sq.py:11: AssertionError
====================== 1 failed, 1 passed in 0.07 seconds ======================

py.test finds all functions starting with "test" prefix and executes each one of them.

In [178]:
!py.test sq.py -v
============================= test session starts ==============================
platform darwin -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- /Users/anand/pyenvs/python35/bin/python3.5
cachedir: .cache
rootdir: /Users/anand/trainings/2017/intuit, inifile: 
collected 2 items 

sq.py::test_square FAILED
sq.py::test_sum_of_squares PASSED

=================================== FAILURES ===================================
_________________________________ test_square __________________________________

    def test_square():
        assert square(0) == 0
        assert square(3) == 9
>       assert square(-3) == 90
E       assert 9 == 90
E        +  where 9 = square(-3)

sq.py:11: AssertionError
====================== 1 failed, 1 passed in 0.07 seconds ======================

Q: Is there way to run tests selectively?

In [180]:
!py.test -k test_square -v sq.py
============================= test session starts ==============================
platform darwin -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- /Users/anand/pyenvs/python35/bin/python3.5
cachedir: .cache
rootdir: /Users/anand/trainings/2017/intuit, inifile: 
collected 2 items 

sq.py::test_square FAILED

=================================== FAILURES ===================================
_________________________________ test_square __________________________________

    def test_square():
        assert square(0) == 0
        assert square(3) == 9
>       assert square(-3) == 90
E       assert 9 == 90
E        +  where 9 = square(-3)

sq.py:11: AssertionError
============================== 1 tests deselected ==============================
==================== 1 failed, 1 deselected in 0.07 seconds ====================

How to install py.test

Python comes with a tool called pip (pip3 for python3) install third-party packages.

pip3 install pytest
In [182]:
!pip3 install pytest
Requirement already satisfied (use --upgrade to upgrade): pytest in /Users/anand/pyenvs/python35/lib/python3.5/site-packages
Requirement already satisfied (use --upgrade to upgrade): py>=1.4.29 in /Users/anand/pyenvs/python35/lib/python3.5/site-packages (from pytest)
You are using pip version 8.1.2, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

Lists (Continued...)

Sorting Lists

In [183]:
names = ["alice", "dave", "bob", "charlie"]
In [184]:
names.sort() # sorts the list in-place. 
In [185]:
names
Out[185]:
['alice', 'bob', 'charlie', 'dave']

Notice that the sort method modifies the list and doesn't return anything.

In [187]:
names = ["alice", "dave", "bob", "charlie"]
sorted(names) # returns a new sorted list
Out[187]:
['alice', 'bob', 'charlie', 'dave']

How to sort these names by length?

In [189]:
sorted(names, key=len)
Out[189]:
['bob', 'dave', 'alice', 'charlie']

How to sort ignoring the case?

In [190]:
names = ["Alice", "bob", "dave", "Charlie"]
In [191]:
sorted(names)
Out[191]:
['Alice', 'Charlie', 'bob', 'dave']
In [193]:
# this is not what we need
sorted([name.lower() for name in names])
Out[193]:
['alice', 'bob', 'charlie', 'dave']
In [196]:
def ignore_case(name):
    print("ignore_case", name)
    # FIXME
    return name.lower()

sorted(names, key=ignore_case)
ignore_case Alice
ignore_case bob
ignore_case dave
ignore_case Charlie
Out[196]:
['Alice', 'bob', 'Charlie', 'dave']

Problem: Print all files in the current directory, sorted by the file size.

Strings

In [197]:
x = "hello"
In [198]:
x[0]
Out[198]:
'h'
In [199]:
for c in x:
    print(c)
h
e
l
l
o
In [200]:
max("helloworld")
Out[200]:
'w'
In [203]:
x
Out[203]:
'hello'
In [204]:
x[:4]
Out[204]:
'hell'

We can use the strip method to remove any characters from both ends of a string.

In [205]:
"  hello  \n".strip() # strip any white space
Out[205]:
'hello'
In [206]:
"  hello  \n".strip("\n") # strip new line
Out[206]:
'  hello  '

String Formatting

In [207]:
name = "Python"
message = "Hello {}!".format(name)
print(message)
Hello Python!
In [208]:
"chapter {}: {}".format(1, "Getting Started")
Out[208]:
'chapter 1: Getting Started'

Sometimes we may want to use the same value multiple times.

In [209]:
t = "chapter {}: {}\nThe contents of {} will come here"
In [212]:
print(t.format(1, "Getting Started", "Getting Started"))
chapter 1: Getting Started
The contents of Getting Started will come here

Instead of specifying multiple times, we can use index.

In [214]:
t = "chapter {0}: {1}\nThe contents of {1} will come here"
print(t.format(1, "Getting Started"))
chapter 1: Getting Started
The contents of Getting Started will come here

It is also possible to specify the values by name.

In [215]:
t = "chapter {index}: {title}\nThe contents of {title} will come here"
print(t.format(index=1, title="Getting Started"))
chapter 1: Getting Started
The contents of Getting Started will come here
In [216]:
x = "Getting Started"

t = "chapter {index}: {title}\nThe contents of {title} will come here"
print(t.format(index=1, title=x))
chapter 1: Getting Started
The contents of Getting Started will come here

Let us look at a simple example.

In [218]:
def make_link(url):
    return '<a href="{url}">{url}</a>'.format(url=url)
In [219]:
make_link("http://google.com")
Out[219]:
'<a href="http://google.com">http://google.com</a>'

Python also supports a legacy string formatting.

In [221]:
print("hello %s" % "Python")
hello Python
In [ ]: