1. Python review

1.1. Introduction

Python is the programming language which can be used for various purposes e.g. web design, mathematical modeling, creating documents and game designs etc. In this chapter, we will review some of the basic features of Python. Then, from next chapter we will write some good coding styles with advance features of Python.

Note

This chapter presents a short review of Python along with some good coding practices. Actual tutorial begins from next chapter. If you have basic knowledge of Python and OOPs then you can skip this chapter.

1.2. Download and Installation

We will use Python 3 in this tutorial. Also, these codes may not run in Python 2.x (e.g. 2.7 or 2.9 etc.) versions.

Further, there are several useful libraries available for Python. For example, Numpy and Scipy libraries contain various mathematical functions which are very useful in simulations. Matplotlib library is required to plot results and save these in various formats e.g. PDF, JPEG or PS etc. Lastly, SPYDER environment is very helpful for storing the results obtained by the simulations. Also, SPYDER can save data as ‘.mat’ file, which can be used by MATLAB software as well. All these topics are briefly discussed in this tutorial.

For installation, simplest option is to download and install Anaconda software, as it contains various useful Python libraries (including the libraries which are mentioned in above paragraph)

1.3. Basics

In this section, ‘print’ command is used to show the code execution in Python. In Python, code can be run in two ways, i.e. through ‘Python shell’ or ‘by executing the Python files’, which are discussed next.

1.3.1. Run code from Python shell (>>>)

Go to terminal/command-prompt and type Python as shown in Listing 1.1. This command will start the Python shell with three ‘greater than’ signs i.e. >>>. Now, we can write our first command i.e. print(‘Hello World’), which prints the ‘Hello World’ on the screen as shown in the listing. Further in line 4, two placeholder ‘%s’ are used, which are replaced by the two words i.e. World and Python as shown in line 5. Also, After executing the command, >>> appear again, which indicates that Python shell is ready to get more commands.

Note

Choose correct command to open Python3

$ python   (Linux)

or

$ python3    (Linux)

or

C:\>python   (in windows)
Listing 1.1 Hello World
1
2
3
4
5
6
>>>
>>> print("Hello World")
Hello World
>>> print("Hello %s, simulation can be done using %s." % ("World", "Python"))
Hello World, simulation can be done using Python.
>>>

1.3.2. Running Python Files

We can also save the code in Python file with extension ‘.py’; which can be run from Python shell. For example, let a file ‘hello.py’ is saved in the folder name as ‘PythonCodes’ at C: drive. Content of the ‘hello.py’ is shown in Listing 1.2. ‘#’ in the file is used for comments (which increases the readability of the code), and has no effect in the execution of the code. To run the ‘hello.py’, we need to locate the folder ‘PythonCodes’ (where we saved the hello.py file) and then execute the file as shown in Listing 1.3.

Note that Python codes work on indentations i.e. each block of code is defined by the line indentation (or spaces). Any wrong space will result in the error which can be seen by uncommenting the line 6 Listing 1.2,

Listing 1.2 hello.py
1
2
3
4
5
6
# hello.py: prints "Hello World"
# save this file to any location e.g. C:\>PythonCodes
print("Hello World")

## following line will be error because of extra space in the beginning
#   print("Hello World with spaces is error") # error
Listing 1.3 Run hello.py
1
2
$ python hello.py
Hello World

Note

Since this method stores the code which can be used later, therefore this method is used throughout the tutorial. Please read the comments in the Listings to understand the codes completely.

1.3.3. Variables

Variables are used to store some data as shown in Listing 1.4. Here, two variables a and b are defined in line 3 and 4 respective, which store the values 3 and 6 respectively. Then various operations are performed on those variables.

Listing 1.4 Variables
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#variableEx.py: a and b are the variables
# which stores 3 and 5 respectively
a=3
b=6

# following line will add value of a i.e. 3 with 6
print(a+6) # 9

# following line will perform (3+5)/2
print((a+b)/2) # 4.5

# following line will perform (3+5)/2
# and then display the interger value only
print(int((a+b)/2)) # 4

1.3.4. Built-in object types

Table 1.1 shows the various built-in object types available in Python. Various operations can be performed on these object depending on their types e.g. add operations can be performed on number-object-type, or collection of data (e.g. username-password-email) can be created using list-object-type etc. All these object types are discussed in this section.

Table 1.1 Built-in object types
Object type Exmaple
Number “3.14, 5, 3+j2”
String ” ‘Python’, ‘make your code’”
List “[1, ‘username’, ‘password’, ‘email’]”
Tuple “(1, ‘username’, ‘password’, ‘email’)”
Dictionary “{‘subject’:Python, ‘subjectCode’:1234}”
File “text=opern(‘Python’, ‘r’).read()”

1.3.5. Numbers

Python supports various number types i.e. integer, float (decimal numbers), octal, hexadecimal and complex numbers as shown in Listing 1.5. The list also shows the method by which one format can be converted to another format.

Listing 1.5 Number formats
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#numFormat.py
a = 11 #integer
print(hex(a)) #0xb

b = 3.2 # float(decimal)
## print(oct(b)) # error: can't convert float to oct or hex.
# integer can be converted, therefore first convert float to int
# and then to hex/oct
print(oct(int(b))) #0o3

d = 0X1A # hexadecimal: '0X' is used before number i.e. 1A
print(d) # 26

#add hex and float
print(b+d) #29.2

c = 0o17 # octal: '0o' is used before number i.e. 17
# print command shows the integer value
print(c) # 15
#to see octal form use `oct'
print(oct(c)) #0o17

e = 3+2j # imagenary
print(e) #(3+2j)
print(abs(e)) #3.6055512754639896
# round above value upto 2 decimal
r=round(abs(e),2)
print(r) #3.61

1.3.6. String

String can be very useful for displaying some output messages on the screen as shown Listing 1.6. Here messages are displayed on screen (using ‘input’ command) to get inputs from user and finally output is shown using %s placeholder (see line 26 for better understand of %s).

Listing 1.6 Strings
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#strEx.py
firstName = "Meher" #firstName is variable of string type
print(firstName) # Meher
fullName = "Meher Krishna Patel"
print(fullName) # Meher Krishna Patel

#input is used to take input from user
score1 = input("Enter the score 1: ")  #enter some value e.g. 12
score2 = input("Enter the score 2: ")  #enter some value e.g. 36
totalString = score1 + score2 # add score1 and score2
messageString = "Total score is %s"
#in below print, totalstring will be assinge to %s of messageString
print(messageString % totalString)  # 1236 (undesired result)

#score1 and score2 are saved as string above
#first we need to convert these into integers as below
totalInt = int(score1) + int(score2)# add score1 and score2
messageString = "Total score is %s"
print(messageString % totalInt)  # 48

#change the input as integer immediately
score1 = int(input("Enter the score 1: "))  #enter some value e.g. 12
score2 = int(input("Enter the score 2: ")) #enter some value e.g. 36
total = score1 + score2 # add score1 and score2
messageString = "score1(%s) + score2[%s] =  %s"
print(messageString % (score1, score2, total)) #score1(12) + score2[36] =  48

1.3.7. List

Variables can store only one data, whereas list can be used to store a collection of data of different types. A list contains items separated by commas, and enclosed within the square brackets [ ]. Listing 1.7 defines a list along with access to it’s elements.

Listing 1.7 List
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#listEx.py
a = [24, "as it is", "abc", 2+2j]
# index start with 0
# i.e. location of number 24 is '0' in the list
print(a[0]) # 24
print(a[2]) # abc

#replace 'abc' with 'xyz'
a[2]='xyz'
print(a[2]) # xyz

# Add 20 at the end of list
a.append(20)
print(a) # [24, 'as it is', 'abc', (2+2j), 20]

1.3.8. Tuple

A tuple is similar to the list. A tuple consists of values separated by commas as shown in Listing 1.8. Tuple can be considered as ‘read-only’ list because it’s values and size can not be changed after defining it.

Listing 1.8 Tuple
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#tupleEx.py
a = 24, "as it is", "abc", 2+2j

## some times () brackets are used to define tuple as shown below
#a = (24, "as it is", "abc", 2+2j)

# index start with 0
# i.e. location of number 24 is '0' in the list
print(a[0]) # 24
print(a[2]) # abc

##Following lines will give error,

##as value can be changed in tuple
#a[2]='xyz' # error

##as size of the tuple can not be changed
# a.append(20) # error

1.3.9. Dictionary

Dictionary can be seen as unordered list with key-value pairs. In the other works, since list’s elements are ordered therefore it’s elements can be access through index as shown in Listing 1.7. On the other hand, location of the elements of the dictionary get changed after defining it, therefore key-value pairs are required, and the values can be accessed using keys as shown in Listing 1.9.

Listing 1.9 Dictionary
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#dictEx.py
myDict = {}         # define new dictionary
myDict[1] = "one"  # 1 is called key; "one" is called value
myDict['a'] = "alphabet"

print(myDict)            # {1: 'one', 'a': 'alphabet'}
print(myDict.items())  # dict_items([(1, 'one'), ('a', 'alphabet')])
print(myDict.keys())   # dict_keys([1, 'a'])
print(myDict.values()) # dict_values(['one', 'alphabet'])

print(myDict[1]) #one
print(myDict['a']) # alphabet

# add key-value while creating the dictionary
subjectDict = {'py': 'Python', 'np': 'Numpy', 'sp':'Scipy'}
print(subjectDict) # {'py': 'Python', 'sp': 'Scipy', 'np': 'Numpy'}

1.4. Number conversion

Following are the patterns to convert the numbers in different formats.

1.4.1. Direct conversion

>>> # decimal to binary conversion with 6 places
>>> print('{:06b}'.format(10))
001010

>>> # if '6b' is used instead of '06b', then initial 0 will not be displayed.
>>> print('{:6b}'.format(10))
  1010

>>> # decimal to hex conversion with 6 places
>>> print('{:06x}'.format(10))
00000a

>>> # binary to hexadecimal with 3 places
>>> print('{:03x}'.format(0b1111))
00f

1.4.2. zfill

>>> # {:b} = binary
>>> print("{:b}".format(10).zfill(15))  # number = 10, total places = 15
000000000001010

>>> x = 10

>>> print("{:b}".format(x).zfill(15))  # number = x, total places = 15
000000000001010

>>> # {:x} = hexadecimal
>>> print("{:x}".format(x).zfill(15))  # number = x, total places = 15
00000000000000a

>>> # {:o} = octal
>>> print("{:o}".format(x).zfill(15))  # number = x, total places = 15
000000000000012


>>> # {:d} = decimal, 0x11 = 17
>>> print("{:d}".format(x).zfill(0x11))  # number = x, total places = 17
00000000000000010

>>> # {:d} = decimal, 0b11 = 3
>>> print("{:d}".format(x).zfill(0b11))  # number = x, total places = 3
010

>>> # {:d} = decimal, 0o11 = 9
>>> print("{:d}".format(x).zfill(0o11))  # number = x, total places = 9
000000010

1.5. Control structure

In this section, various simple Python codes are shown to explain the control structures available in Python.

1.5.1. if-else

‘If-else’ statements are used to define different actions for different conditions. Symbols for such conditions are given in Table 1.2, which can be used as shown in Listing 1.11. Three examples are shown in this section for three types of statements i.e. if, if-else and if-elif-else.

Table 1.2 Symbols for conditions
Condition Symbol
equal ==
not equal !=
greater >
smaller <
greater or equal >=
smaller or equal <=

1.5.1.1. If statement

In this section, only if statement is used to check the even and odd number.

Listing 1.10 checks whether the number stored in variable ‘x’ is even or not.

Listing 1.10 If statement
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#ifEx.py
x = 2
# brackets are not necessary (used for clarity of the code)
if (x%2 == 0): # % sign gives the value of the remainder e.g. 5%3 = 2
    #if above condition is true, only then following line will execute
    print('Number is even.')

#this line will execute always as it is not inside 'if'
print('Bye Bye')

'''
Number is even.
Bye Bye
'''

'''
if you put x = 3, then output will be,
Bye Bye
i.e. Number is even will not be printed as 'if' condition is not satisfied.
'''

Explanation Listing 1.10

An if statement is made up of the ‘if’ keyword, followed by the condition and colon (:) at the end, as shown in line 4. Also, the line 6 is indented which means it will execute only if the condition in line 4 is satisfied (see Listing 1.13 for better understanding of indentation). Last print statement has no indentation, therefore it is not the part of the ‘if’ statement and will execute all the time. You can check it by changing the value of ‘x’ to 3 in line 2. Three quotes (in line 11 and 14) are used to comment more than one lines in the code, e.g. ‘’’ results here ‘’’ is used at the end of the code (see line 11-20) which contain the results of the code.

Warning

Note that, the comments inside the ‘’’ is known as ‘Docstrings’, which are displayed when help command is used. Therefore, do not use it for multiline comments. It is better to use # at each line.

1.5.1.2. Multiple If statements

Listing 1.11 checks the even and odd numbers using ‘if’ statements. Since there are two conditions (even and odd), therefore two ‘if’ statements are used to check the conditions as shown in Listing 1.11. Finally output is shown as the comments at the end of the code.

Listing 1.11 Multiple If statements
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#ifOnly.py: uses multiple if to check even and odd nubmer

# "input" command takes input as string
# int is used for type conversion
x=int(input('Enter the number:\t')) #\t is used for tab in the output

# % is used to calculate remainder
if x%2==0:
    print ("Number is even")
if x%2!=0:
    print ("Number is odd")

'''
Output-1st run:
Enter the number:   10
Number is even

Output-2nd run:
Enter the number:   15
Number is odd
'''

Explanation Listing 1.11

This code demonstrate that one can use multiple ‘if’ conditions in codes. In this code, value of ‘x’ is taken from using line 5 in the code. ‘int’ is used in this line because ‘input’ command takes the value as string and it should be changed to integer value for mathematical operations on it.

1.5.1.3. If-else

As we know that a number can not be even and odd at the same time. In such cases we can use ‘if-else’ statement.

Code in Listing 1.11 can be written using If-else statement as show in Listing 1.12.

Listing 1.12 If-else statement
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# ifelse1.py: use if-else to check even and odd nubmer

# "input" command takes input as string
x= int(input('Enter the number:\t'))

# % is used to calculate remainder
if x%2==0:
    print("Number is even")
else:
    print("Number is odd")

'''
Output-1st run:
Enter the number:   10
Number is even

Output-2nd run:
Enter the number:   15
Number is odd
'''

Explanation Listing 1.12

Line 4 takes the input from the user. After that remainder is checked in line 7. If condition is true i.e. remainder is zero, then line 8 will be executed; otherwise print command inside ‘else’ statement (line 10) will be executed.

1.5.1.4. If-elif-else

In previous case, there were two contions which are implemented using if-else statement. If there are more than two conditions then ‘elif’ block can be used as shown in next example. Further, ‘If-elif-else’ block can contain any number of ‘elif’ blocks between one ‘if’ and one ‘else’ block.

Listing 1.13 checks whether the number is divisible by 2 and 3, using nested ‘if-else’ statement.

Listing 1.13 If-elif-else statement
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#elif.py: checks divisibility with 2 and 3

# "int(input())" command takes input as number
x=int(input('Enter the number:\t'))

# % is used to calculate remainder
if x%2==0: #check divisibility with 2
    if x%3==0: # if x%2=0, then check this line
        print("Number is divisible by 2 and 3")
    else:
        print("Number is divisible by 2 only")
        print("x%3= ", x%3)
elif x%3==0: #check this if x%2 is not zero
    print("Number is divisible by 3 only")
else:
    print("Number is not divisible by 2 and 3")
    print("x%2= ", x%2)
    print("x%3= ", x%3)
print("Thank you")

'''
output 1:
Enter the number:   12
Number is divisible by 2 and 3
Thank you

output 2:
Enter the number:   8
Number is divisible by 2 only
x%3=  2
Thank you

output 3:
Enter the number:   7
Number is not divisible by 2 and 3
x%2=  1
x%3=  1
Thank you

output 4:
Enter the number:   15
Number is divisible by 3 only
Thank you
'''

Explanation Listing 1.13

Let’s discuss the indentation first. First look at the indentation at lines ‘7’ and ‘8’. Since line ‘8’ is shifted by one indentation after line ‘7’, therefore it belongs to line ‘7’, which represents that Python-interpreter will go to line ‘8’ only if line ‘7’ is true. Similarly, print statements at line ‘11’ and ‘12’ are indented with respect to line ‘10’, therefore both the print statement will be executed when Python-interpreter reaches to ‘else’ condition.

Now we will see the output for ‘x=12’. For ‘x=12’, ‘if’ statement is satisfied at line ‘7’, therefore Python-interpreter will go to line ‘8’, where the divisibility of the number is checked with number ‘3’. The number is divisible by ‘3’ also, hence corresponding print statement is executed as shown in line ‘24’. After this, Python-interpreter will exit from the ‘if-else’ statements and reached to line ‘19’ and output at line ‘25’ will be printed.

Lets consider the input ‘x=7’. In this case number is not divisible by ‘2’ and ‘3’. Therefore Python-interpreter will reached to line ‘15’. Since lines ‘16’, ‘17’ and ‘18’ are indented with respect to line ‘15’, hence all the three line will be printed as shown in line ‘35-37’ . Finally, Python-interpreter will reach to line ‘19’ and print this line also.

Lastly, use 15 as the input number. Since it is not divided by 2, it will go to elif statement and corresponding print statement will be executed.

Since there are three conditions in this example. therefore ‘elif’ statement is used. Remember that ‘if-else’ can contain only one ‘if’ and one ‘else’ statement , but there is no such restriction of ‘elif’ statement. Hence, if there higher number of conditions, then we can increase the number of ‘elif’ statement.

Listing 1.13 can be written as Listing 1.14 and Listing 1.15 as well. Here ‘or’ and ‘and’ keywords are used to verify the conditions. The ‘and’ keyword considers the statement as true, if and only if, all the conditions are true in the statement; whereas ‘or’ keyword considers the statement as true, if any of the conditions are true in the statement.

Listing 1.14 ‘and’ logic
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#andLogic.py: check divisibility with 2 and 3
x=int(input('Enter the number:\t'))

if x%2==0 and x%3==0: #check divisibility with both 2 and 3
    print("Number is divisible by 2 and 3")
elif x%2==0: #check this if x%2 is not zero
    print("Number is divisible by 2 only")
elif x%3==0: #check this if x%3 is not zero
    print("Number is divisible by 3 only")
else:
    print("Number is not divisible by 2 and 3")
    print("x%2= ", x%2)
    print("x%3= ", x%3)
print("Thank you")
Listing 1.15 ‘and’ and ‘or’ logic
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#orLogic.py: check divisibility with 2 and 3
x=int(input('Enter the number:\t'))

# % is used to calculate remainder
if x%2==0 or x%3==0: #check divisibility with 2 or 3
    if x%2==0 and x%3==0: #check if divided by both 2 and 3
        print("Number is divisible by 2 and 3")
    elif x%2==0: #check this if x%2 is not zero
        print("Number is divisible by 2 only")
    elif x%3==0: #check this if x%3 is not zero
        print("Number is divisible by 3 only")
else:
    print("Number is not divisible by 2 and 3")
    print("x%2= ", x%2)
    print("x%3= ", x%3)
print("Thank you")

1.5.2. While loop

‘While’ loop is used for recursive action, and the loop repeat itself until a certain condition is satisfied.

Listing 1.16 uses ‘while’ loop to print numbers 1 to 5. For printing numbers upto 5, value of initial number should be increased by 1 at each iteration, as shown in line 7.

Listing 1.16 While loop
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#WhileExample1.py: Print numbers upto 5

n=1 #initial value of number
print("Numbers upto 5: ")
while n<6:
    print(n, end=" "), #end=" ": to stop row change after print
    n=n+1
print("\nCode ended at n =  %s" % n)

'''
output:
Numbers upto 5:
1 2 3 4 5
Code ended at n =  6
'''

Explanation Listing 1.16

In the code, line ‘3’ sets the initial value of the number i.e. ‘n=1’. Line ‘5’ indicates that ‘while’ loop will be executed until ‘n’ is less than 6. Next two lines i.e. line ‘6’ and ‘7’, are indented with respect to line ‘5’. Hence these line will be executed if and only if the condition at line ‘5’ is satisfied.

Since the value of ‘n’ is one therefore while loop be executed. First, number 1 is printed by line ‘6’, then value of ‘n’ is incremented by 1 i.e. ‘n=2’. ‘n’ is still less than 6, therefore loop will be executed again. In this iteration value of ‘n’ will become 3, which is still less than 6. In the same manner, loop will continue to increase the value of ‘n’ until ‘n=6’. When ‘n=6’, then loop condition at line ‘5’ will not be satisfied and loop will not be executed this time. At this point Python-interpreter will reach to line ‘8’, where it will print the value stored in variable ‘n’ i.e. 6 as shown in line ‘14’.

Also, look at ‘print’ commands at lines ‘3, 6’ and ‘8’. At line ‘6’, “end = ‘ ‘” is placed at the end, which results in no line change while printing outputs as shown at line ‘13’. At line ‘8’, ‘\n’ is used to change the line, otherwise this print output will be continued with output of line ‘6’.

1.5.3. For loop

Repetitive structure can also be implemented using ‘for’ loop. For loop requires the keyword in and some sequence for execution. Lets discuss the range command to generate sequences, then we will look at ‘for’ loop. Some outputs of ‘range’ commands are shown in Listing 1.17.

Listing 1.17 Range command
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> range(5)
[0, 1, 2, 3, 4]

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

>>> range(11, 19, 2)
[11, 13, 15, 17]

>>> range(15, 7, -2)
[15, 13, 11, 9]

Explanation Listing 1.17

From the outputs of ‘range’ commands in the listing, it is clear that it generates sequences of integers. Python indexing starts from zero, therefore command ‘range(5)’ at line ‘3’ generates five numbers ranging from ‘0’ to ‘4’.

At line ‘6’, two arguments are given in ‘range’ commands i.e. ‘1’ and ‘4’. Note that output for this at line ‘7’ starts from ‘1’ and ends at ‘3’ i.e. last number is not included by Python in the output list.

At line ‘9’ and ‘12’, three arguments are provided to ‘range’ function. In these cases, third argument is the increment value e.g. line ‘12’ indicates that the number should start from ‘15’ and stop at number ‘7’ with a decrement of ‘2’ at each step. Note that last value i.e. ‘7’ is not included in the output again. Similarly, output of line ‘9’ does not include ‘19’.

Listing 1.18 prints numbers from ‘1’ to ‘5’ in forward and reverse direction using range command.

Listing 1.18 For loop
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ForExample1.py: Print numbers 1-5

print("Numbers in forward order")
for i in range(5):
    print(i+1, end=" ")
print("\nFinal value of i is: ", i)

print ("\nNumbers in reverse order")
for j  in range(5, 0, -1):
    print(j, end=" "),
print("\nFinal value of i is: ", j)

'''
outputs:
Numbers in forward order
1 2 3 4 5
Final value of i is:  4

Numbers in reverse order
5 4 3 2 1
Final value of i is:  1
'''

fruits=["apple", "banana", "orange"]
print("List of fruits are shown below:")
for i in fruits:
    print(i)
'''
List of fruits are shown below:
apple
banana
orange
'''

Explanation Listing 1.18

At line ‘4’, command ‘range(5)’ generates the five numbers, therefore loop repeats itself five times. Since, output of range starts from ‘0’, therefore ‘i’ is incremented by one before printing. Line ‘6’ shows that the variable ‘i’ stores only one value at a time, and the last stored value is ‘4’ i.e. last value of ‘range(5)’.

At line ‘9’, variable ‘j’ is used instead of ‘i’ and range command generates the number from 1 to 5 again but in reverse order. Note that number of iteration in for loop depends on the number of elements i.e. length of the ‘range’ command’s output and independent of the element values. Line ‘10’ prints the current value of ‘j’ at each iteration. Finally, line ‘15’ prints the last value stores in variable ‘j’ i.e. ‘j=1’, which is the last value generated by command ‘range(5,0,-1)’.

Code in line ‘24’ shows that, how the values are assigned to the iterating variable ‘i’ from a list. The list ‘fruits’ contains three items, therefore loop will execute three times; and different elements of the list are assigned to variable ‘i’ at each iteration i.e. apple is assign first, then banana and lastly orange will be assigned.

1.6. Function

Some logics may be used frequently in the code, which can be written in the form of functions. Then, the functions can be called whenever required, instead of rewriting the logic again and again.

In Listing 1.19, the function ‘addTwoNum’ adds two numbers.

Listing 1.19 Function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#funcEx.py
def addTwoNum(a, b):
    sum = a+b
    return(sum)

result = addTwoNum(3,4)
print("sum of numbers is %s" % result)

'''
sum of numbers is 7
'''

Explanation Listing 1.19

In Python, function is defined using keyword ‘def’. Line 2 defines the function with name ‘addTwoNum’ which takes two parameter i.e. a and b. Line 3 add the values of ‘a’ and ‘b’ and stores the result in variable ‘sum’. Finally line 4 returns the value to function call which is done at line 6.

In line 6, function ‘addTwoNum’ is called with two values ‘4’ and ‘5’ which are assigned to variable ‘a’ an ‘b’ respectively in line 2. Also, function returns the ‘sum’ variable from line 4, which is stored in variable ‘results’ in line 6 (as line 6 called the function). Finally, line 7 prints the result.

In Listing 1.20, the function is defined with some default values; which means if user does not provide all the arguments’ values, then default value will be used for the execution of the function.

Listing 1.20 Function with default arguments
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#funcEx2.py: default argument can be defined only after non-default argument
# e.g. addTwoNum(num1=2, num2): is wrong. b must have some defined value
def addTwoNum(num1, num2=2):
    return(num1+num2)

result1 = addTwoNum(3)
print("result1=%s" % result1)

result2 = addTwoNum(3,4)
print("result2=%s" % result2)

'''
result1=5
result2=7
'''

Explanation Listing 1.20

Function of this listing is same as Listing 1.19. Only difference is that the line 3 contains a default value for num2 i.e. ‘num2 = 2’. Default value indicates that, if function is called without giving the second value then it will be set to 2 by default, as shown in line 6. Line 6 pass only one value i.e. 3, therefore num1 will be assign 3, whereas num2 will be assigned default value i.e. 2. Rest of the the working is same as Listing 1.19.

Note

There are various other important Python features e.g. classes, decorators and descriptors etc. which are not explained here as we are not going to use these in the coding. Further, using these features we can make code more efficient and reusable along with less error-prone.

1.7. Numpy, Scipy and Matplotlib

In this section, we will use various Python libraries, i.e. Numpy, Scipy and Matplotlib, which are very useful for scientific computation. With Numpy library, we can define array and matrices easily. Also, it contains various useful mathematical function e.g. random number generator i.e. ‘rand’ and ‘randn’ etc. Matplotlib is used for plotting the data in various format. Some of the function of these libraries are shown in this section. Further, Scipy library can be used for more advance features e.g. complementary error function (erfc) and LU factorization etc.

Listing 1.21 generates the sine wave using Numpy library; whereas the Matplotlib library is used for plotting the data.

Listing 1.21 Sine wave using Numpy and Matplotlib, Fig. 1.1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# numpyMatplot.py
import numpy as np
import matplotlib.pyplot as plt
# np.linspace: devide line from 0 to 4*pi into 100 equidistant points
x = np.linspace(0, 4*np.pi, 100)
sinx = np.sin(x) # find sin(x) for above 100 points
plt.plot(x,sinx) # plot (x, sin(x))
plt.xlabel("Time") # label for x axis
plt.ylabel("Amplitude") # label for y axis
plt.title('Sine wave') # title
plt.xlim([0, 4*np.pi]) # x-axis display range
plt.ylim([-1.5, 1.5]) # y-axis display range
plt.show() # to show the plot on the screen

Explanation Listing 1.21

First line import numpy library to the code. Also, it is imported with shortname ‘np’; which is used in line 5 as ‘np.linspace’. If line 2 is written as ‘import numpy’, then line 5 should be written as ‘numpy.linspace’. Further, third line import ‘pyplot’ function of ‘matplotlib’ library as plt. Rest of the lines are explained as comments in the listing.
../_images/SineWave.jpg

Fig. 1.1 Sine wave using Numpy and Matplotlib, Listing 1.21

1.7.1. Arrays

Arrays can be created using ‘arange’ and ‘array’ commands as shown below,

1.7.1.1. arange

One dimensional array can be created using ‘arange’ option as shown below,

Listing 1.22 arange
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# arangeEx.py
import numpy as np
a=np.arange(1,10) # last element i.e. 10 is not included in result
print(a) # [1 2 3 4 5 6 7 8 9]
print(a.shape) # (9,) i.e. total 9 entries

b=np.arange(1,10,2) # print 1 to 10 with the spacing of 2
print(b) # [1 3 5 7 9]
print(b.shape) # (5,) i.e. total 9 entries

c=np.arange(10, 2, -2) # last element 2 is not included in result self
print(c) # [10  8  6  4]

1.7.1.2. array

Multidimensional array can not be created by ‘arange’. Also, ‘arange’ can only generate sequences and can not take user-defined data. These two problems can be solved by using ‘array’ option as shown in Listing 1.23,

Listing 1.23 array
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# arrayEx.py
import numpy as np

a= np.array([1, 8, 2])
print(a) # [1 8 2]
print(np.shape(a)) # (3,)

b=np.array([
    [1, 2],
    [4, 3],
    [6, 2]
])
# b can be written as follow as well, but above is more readable
# b=np.array([[1, 2],[4, 3]])
print(np.shape(b)) #(3, 2) i.e. 3 row and 2 column

# row of array can have different number of elements
c=np.array([[np.arange(1,10)],[np.arange(11, 16)]])
print(c)
'''
[[array([1, 2, 3, 4, 5, 6, 7, 8, 9])]
     [array([11, 12, 13, 14, 15])]]
'''

1.7.1.3. Matrix

Similar to array, we can define matrix using ‘mat’ function of numpy library as shown in Listing 1.24. Also, LU factorization of the matrix is shown in Listing 1.25 using Scipy library. There are differences in results of mathematical operations on the matrices defined by ‘array’ and ‘mat’, as shown in the Listing 1.25; e.g. ‘dot’ function is required for matrix multiplication of array; whereas ‘*’ sign performs matrix multiplication for ‘mat’ function.

Listing 1.24 Matrix
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# matrixEx.py
import numpy as np
from scipy.linalg import lu

a= np.mat('1, 2; 3, 2; 2, 3') # define matrix
# print(np.shape(a)) # (3, 2)

aT = np.transpose(a) # transpose of matrix 'a'
# print(np.shape(aT)) # (2, 3)

# eye(n) is used for (nxn) Identity matrix
b=2*np.eye(3) # 2 * Identity matrix
# print(np.shape(b)) # (3, 3)

c = b*a
# print(np.shape(c)) # (3, 2)

l= lu(a)
print(l)
Listing 1.25 LU Factorization of Matrix
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# scipyEx.py
import numpy as np
# import LU factorization command from scipy.linalg
from scipy.linalg import lu

#define matrix 'a'
a= np.mat('1, 1, 1; 3, 4, 6; 2, 5, 4') # define matrix

# perform LU factorization and
# save the values in p, l and u as it returns 3 values
[p, l, u]= lu(a)

# print values of p, l and u
print("p = ", p)
print("l = ", l)
print("u = ", np.round(l,2))


print("Type of P: ", type(p)) #type of p: ndarray
# p*l*u will give wrong results
# because types are not matrix (but ndarray) as shown above
r = p.dot(l).dot(u)
print("r = ", r)

#for p*l*u we need to change the ndarray to matrix type as below,
print("Type of P after np.mat: ", type(np.mat(p)))
m = np.mat(p)*np.mat(l)*np.mat(u)
print("m = ", m)

'''
Outputs:

p =  [[ 0.  0.  1.]
 [ 1.  0.  0.]
 [ 0.  1.  0.]]

l =  [[ 1.          0.          0.        ]
 [ 0.66666667  1.          0.        ]
 [ 0.33333333 -0.14285714  1.        ]]

u =  [[ 1.    0.    0.  ]
 [ 0.67  1.    0.  ]
 [ 0.33 -0.14  1.  ]]

Type of P:  <class 'numpy.ndarray'>

r =  [[ 1.  1.  1.]
 [ 3.  4.  6.]
 [ 2.  5.  4.]]

Type of P after np.mat:  <class 'numpy.matrixlib.defmatrix.matrix'>

m =  [[ 1.  1.  1.]
 [ 3.  4.  6.]
 [ 2.  5.  4.]]
'''

1.8. Good practices

As oppose to other programming languages, Python provides various ways to iterate over the list, which are shown in this section.

1.8.1. Avoid range command

# multiply 2 to all elements of arr
arr = [10, 20, 30]

# bad practice
for i in range(len(arr)):
    print(2*arr[i])  # 20, 40, 60

# good practices
for i in arr:
    print(2*i)  # 20, 40, 60

# print in reverse order
for i in reversed(arr):
    print(2*i)  # 60, 40, 20

1.8.2. Enumerate

In previous case, we do not have the access over index. Use ‘enumerate’ to get access to index as well,

# multiply 2 to all elements of arr
arr = [10, 20, 30]

for i, a in enumerate(arr):
    print(i, ':', 2*a)
    # 0 : 20
    # 1 : 40
    # 2 : 60

1.8.3. Loop in sorted order

# multiply 2 to all elements of arr,
# but in sorted order
arr = [10, 30, 50, 20]

for i in sorted(arr):
    print(2*i)  # 20, 40, 60, 100

# in reversed sorted order
for i in sorted(arr, reverse=True):
    print(2*i)  # 100, 60, 40, 20

1.8.4. Loop over keys

dc = { 'Toy':3, 'Play':4, 'Games':5}

# print keys of dictionaries
for d in dc:
    print(d)

1.8.5. Loop over keys and values

dc = { 'Toy':3, 'Play':4, 'Games':5}

# print keys, values of dictionaries
for k, v in dc.items():
    print(k, v)
    # Toy 3
    # Play 4
    # Games 5

1.8.6. Create dictionaries from lists

k = ['Toy', 'Game', 'Tiger']
v = ['Toys', 'Games', 'Tigers']

#create dict
dc = dict(zip(k, v))
print(dc)
# {'Game': 'Games', 'Tiger': 'Tigers', 'Toy': 'Toys'}

d = dict(enumerate(v))
print(d)
# {0: 'Toys', 1: 'Games', 2: 'Tigers'}

1.8.7. Looping and modifying the list simultaneously

We need to make a copy of the list for such operations as shown below,

# loopUpdate.py

animals = ['tiger', 'cat', 'dog']
am = animals.copy()

# below line will go in infinite loop
# for a in animals:
for a in am:
    if len(a) > 3:
        animals.append(a)

print(animals)

Or we can use ‘animals[:]’ in the for loop, instead of ‘animal’ as shown below,

# loopUpdate.py

animals = ['tiger', 'cat', 'dog']

for a in animals[:]:
    if len(a) > 3:
        animals.append(a)

print(animals)

1.8.8. Check items in the list

The ‘in’ keyword can be used with ‘if’ statement, to check the value in the list,

# loopUpdate.py

def testNumber(num):
    if num in [1, 3, 5]:
        print("Thanks")
    else:
        print("Number is not 1, 3 or 5")

testNumber(3)
testNumber(4)

1.8.9. Unpacking

Any iterable i.e. list, tuple or set can be unpacked using assignment operator as below,

>>> x = [1, 2, 3]
>>> a, b, c = x
>>> a
1
>>> b
2
>>> student  = ["Tom", 90, 95, 98, 30]
>>> Name, *Marks, Age = student
>>> Marks
[90, 95, 98]
>>> y = (1, "Two", 3, ("Five", "Six", "Seven"))
>>> a, *b, (*c, d) = y
>>> d
'Seven'
>>> c
['Five', 'Six']

1.8.10. Update variables

x = 3
y = 2
z = 5
x, y, z = y, z, x

print(x, y, z)  # 2, 5, 3

1.9. Object oriented programming

Object oriented programming (OOP) increases the re-usability of the code. Also, the codes become more manageable than non-OOP methods. But, it takes proper planning, and therefore longer time, to write the codes using OOP method. In this chapter, we will learn various terms used in OOP along with their usages with examples.

1.9.1. Class and object

A ‘class’ is user defined template which contains variables, constants and functions etc.; whereas an ‘object’ is the instance (or variable) of the class. In simple words, a class contains the structure of the code, whereas the object of the class uses that structure for performing various tasks, as shown in this section.

1.9.2. Create class and object

Class is created using keyword ‘class’ as shown in Line 4 of Listing 1.26, where the class ‘Jungle’ is created. As a rule, class name is started with uppercase letter, whereas function name is started with lowercase letter. Currently, this class does not have any structure, therefore keyword ‘pass’ is used at Line 5. Then, at Line 8, an object of class i.e. ‘j’ is created; whose value is printed at Line 9. This print statement prints the class-name of this object along with it’s location in the memory (see comments at Line 9).

Listing 1.26 Create class and object
1
2
3
4
5
6
7
8
9
#ex1.py

#class declaration
class Jungle:
    pass

# create object of class Jungle
j = Jungle()
print(j)  # <__main__.Jungle object at 0x004D6970>

1.9.2.1. Add function to class

Now, we will add one function ‘welcomeMessage’ in the class. The functions inside the class are known as ‘methods’. The functions inside the class are the normal functions (nothing special about them), as we can see at Lines 5-6. To use the variables and functions etc. outside the class, we need to create the object of the class first, as shown in Line 9, where object ‘j’ is created. When we create teh object of a class, then all the functions and variables of that class is attached to the object and can be used by that object; e.g. the object ‘j’ can now use the function ‘welcomeMessage’ using ‘.’ operator, as shown in Line 10. Also, ‘self’ is used at Line 6, which is discussed in Section Section 1.9.2.2.

Listing 1.27 Add function to class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#ex2.py

#class declaration
class Jungle:
    def welcomeMessage(self):
        print("Welcome to the Jungle")

# create object of class Jungle
j = Jungle()
j.welcomeMessage() # Welcome to the Jungle

1.9.2.2. Constructor

The ‘__init__’ method is used to define and initialize the class variables. This ‘’__init__’ method is known as Constructor and the variables are known as attributes. Note that, the self keyword is used in the ‘init function’ (Line 6) along with the name of the variables (Line 7). Further All the functions, should have first parameter as ‘self’ inside the class. Although we can replace the word ‘self’ with any other word, but it is good practice to use the word ‘self’ as convention.

Explanation Listing 1.28

Whenever, the object of a class is create then all the attributes and methods of that class are attached to it; and the constructor i.e. ‘__init__’ method is executed automatically. Here, the constructor contains one variable i.e. ‘visitorName’ (Line 7) and one input parameter i.e. ‘name’ (Line 6) whose value is initialized with ‘unknown’. Therefore, when the object ‘j’ is created at Line 13, the value ‘Meher’ will be assigned to parameter ‘name’ and finally saved in ‘visitorName’ as constructor is executed as soon as the object created. Further, if we create the object without providing the name i.e. ‘j = Jungle()’, then default value i.e. ‘unknown’ will be saved in attribute ‘visitorName’. Lastly, the method ‘welcomeMessage’ is slightly updated, which is now printing the name of the ‘visitor’ (Line 10) as well.

Listing 1.28 Constructor with default values
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#ex3.py

#class declaration
class Jungle:
    #constructor with default values
    def __init__(self, name="Unknown"):
        self.visitorName = name

    def welcomeMessage(self):
        print("Hello %s, Welcome to the Jungle" % self.visitorName)

# create object of class Jungle
j = Jungle("Meher")
j.welcomeMessage() # Hello Meher, Welcome to the Jungle

# if no name is passed, the default value i.e. Unknown will be used
k = Jungle()
k.welcomeMessage() # Hello Unknown, Welcome to the Jungle

1.9.2.3. Define ‘main’ function

The above code can be written in ‘main’ function (Lines 12-19) using standard-boiler-plate (Lines 22-23), which makes the code more readable, as shown in Listing 1.29. This boiler-plate tells the Python-interpretor that the ‘main’ is the starting point of the code.

Listing 1.29 main function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ex4.py

#class declaration
class Jungle:
    #constructor with default values
    def __init__(self, name="Unknown"):
        self.visitorName = name

    def welcomeMessage(self):
        print("Hello %s, Welcome to the Jungle" % self.visitorName)

def main():
    # create object of class Jungle
    j = Jungle("Meher")
    j.welcomeMessage() # Hello Meher, Welcome to the Jungle

    # if no name is passed, the default value i.e. Unknown will be used
    k = Jungle()
    k.welcomeMessage() # Hello Unknown, Welcome to the Jungle

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.2.4. Keep classes in separate file

To make code more manageable, we can save the class-code (i.e. class Jungle) and application-files (i.e. main) in separate file. For this, save the class code in ‘jungleBook.py’ file, as shown in Listing 1.30; whereas save the ‘main()’ in ‘main.py’ file as shown in Listing 1.31. Since, class is in different file now, therefore we need to import the class to ‘main.py file’ using keyword ‘import’ as shown in Line 4 of Listing 1.31.

Warning

Here, we kept the class and main function in separate files. It is not a good idea keep these small related-codes separate like this. We will learn to manage the code as we will write some big codes in the tutorial.

Listing 1.30 Save classes in separate file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#jungleBook.py

#class declaration
class Jungle:
    #constructor with default values
    def __init__(self, name="Unknown"):
        self.visitorName = name

    def welcomeMessage(self):
        print("Hello %s, Welcome to the Jungle" % self.visitorName)
Listing 1.31 Import class to main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#main.py

#import class 'Jungle' from jungleBook.py
from jungleBook import Jungle

def main():
    # create object of class Jungle
    j = Jungle("Meher")
    j.welcomeMessage() # Hello Meher, Welcome to the Jungle

    # if no name is passed, the default value i.e. Unknown will be used
    k = Jungle()
    k.welcomeMessage() # Hello Unknown, Welcome to the Jungle

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.3. Inheritance

Suppose, we want to write a class ‘RateJungle’ in which visitor can provide ‘rating’ based on their visiting-experience. If we write the class from the starting, then we need define attribute ‘visitorName’ again; which will make the code repetitive and unorganizable, as the visitor entry will be at multiple places and such code is more prone to error. With the help of inheritance, we can avoid such duplication as shown in Listing 1.32; where class Jungle is inherited at Line 12 by the class ‘RateJungle’. Now, when the object ‘r’ of class ‘RateJungle’ is created at Line 7 of Listing 1.33, then this object ‘r’ will have the access to ‘visitorName’ as well (which is in the parent class).

Listing 1.32 Inheritance
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#jungleBook.py

#class declaration
class Jungle:
    #constructor with default values
    def __init__(self, name="Unknown"):
        self.visitorName = name

    def welcomeMessage(self):
        print("Hello %s, Welcome to the Jungle" % self.visitorName)

class RateJungle(Jungle):
    def __init__(self, name, feedback):
        # feedback (1-10) :  1 is the best.
        self.feedback = feedback # Public Attribute

        # inheriting the constructor of the class
        super().__init__(name)

    # using parent class attribute i.e. visitorName
    def printRating(self):
        print("Thanks %s for your feedback" % self.visitorName)
Listing 1.33 Usage of parent-class method and attributes in child-class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#main.py

## import class 'Jungle' and 'RateJungle' from jungleBook.py
from jungleBook import Jungle, RateJungle

def main():
    r = RateJungle("Meher", 3)

    r.printRating() # Thanks Meher for your feedback

    # calling parent class method
    r.welcomeMessage() # Hello Meher, Welcome to the Jungle

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.4. Polymorphism

In OOP, we can use same name for methods and attributes in different classes; the methods or attributes are invoked based on the object type; e.g. in Listing 1.34, the method ‘scarySound’ is used for class ‘Animal’ and ‘Bird’ at Lines 4 and 8 respectively. Then object of these classes are created at Line 8-9 of Listing 1.35. Finally, method ‘scarySound’ is invoked at Lines 12-13; here Line 13 is the object of class Animal, therefore method of that class is invoked and corresponding message is printed. Similarly, Line 14 invokes the ‘scaryMethod’ of class Bird and corresponding line is printed.

Listing 1.34 Polymorphism example with function ‘move’
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#scarySound.py

class Animal:
    def scarySound(self):
        print("Animals are running away due to scary sound.")

class Bird:
    def scarySound(self):
        print("Birds are flying away due to scary sound.")

# scaryScound is not defined for Insect
class Insect:
    pass
Listing 1.35 Polymorphism: move function works in different ways for different class-objects
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#main.py

## import class 'Animal, Bird' from scarySound.py
from scarySound import Animal, Bird

def main():
    # create objects of Animal and Bird class
    a = Animal()
    b = Bird()

    # polymorphism
    a.scarySound() # Animals are running away due to scary sound.
    b.scarySound() # Birds are flying away due to scary sound.

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.5. Abstract class and method

Abstract classes are the classes which contains one or more abstract method; and abstract methods are the methods which does not contain any implemetation, but the child-class need to implement these methods otherwise error will be reported. In this way, we can force the child-class to implement certain methods in it. We can define, abstract classes and abstract method using keyword ‘ABCMeta’ and ‘abstractmethod’ respectively, as shown in Lines 6 and 15 respectively of Listing 1.36. Since, ‘scarySound’ is defined as abstractmethod at Line 15-17, therefore it is compulsory to implement it in all the subclasses.

Note

Look at the class ‘Insect’ in Listing 1.34, where ‘scarySound’ was not defined but code was running correctly; but now the ‘scarySound’ is abstractmethod, therefore it is compulsory to implement it, as done in Line 16 of Listing 1.37.

Listing 1.36 Abstract class and method
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#jungleBook.py

from abc import ABCMeta, abstractmethod

#Abstract class and abstract method declaration
class Jungle(metaclass=ABCMeta):
    #constructor with default values
    def __init__(self, name="Unknown"):
        self.visitorName = name

    def welcomeMessage(self):
        print("Hello %s, Welcome to the Jungle" % self.visitorName)

    # abstract method is compulsory to defined in child-class
    @abstractmethod
    def scarySound(self):
        pass
Listing 1.37 Abstract methods are compulsory to define in child-class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#scarySound.py

from jungleBook import Jungle

class Animal(Jungle):
    def scarySound(self):
        print("Animals are running away due to scary sound.")

class Bird(Jungle):
    def scarySound(self):
        print("Birds are flying away due to scary sound.")

# since Jungle is defined as metaclass
# therefore all the abstract methods are compulsory be defined in child class
class Insect(Jungle):
    def scarySound(self):
        print("Insects do not care about scary sound.")
Listing 1.38 Main function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#main.py

## import class 'Animal, Bird' from scarySound.py
from scarySound import Animal, Bird, Insect

def main():
    # create objects of Animal and Bird class
    a = Animal()
    b = Bird()
    i = Insect()

    # polymorphism
    a.scarySound() # Animals are running away due to scary sound.
    b.scarySound() # Birds are flying away due to scary sound.
    i.scarySound() # Insects do not care about scary sound.

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.6. Public and private attribute

There is not concept of private attribute in Python. All the attributes and methods are accessible to end users. But there is a convention used in Python programming i.e. if a variable or method name starts with ‘_’, then users should not directly access to it; there must be some methods provided by the class-author to access that variable or method. Similarly, ‘__’ is designed for renaming the attribute with class name i.e. the attribute is automatically renamed as ‘_className__attributeName’. This is used to avoid conflict in the attribute names in different classes, and is useful at the time of inheritance, when parent and child class has same attribute name.

Listing 1.39 and Listing 1.40 show the example of attributes along with the methods to access them. Please read the comments to understand these codes.

Listing 1.39 Public and private attribute
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#jungleBook.py

#class declaration
class Jungle:
    #constructor with default values
    def __init__(self, name="Unknown"):
        self.visitorName = name

    def welcomeMessage(self):
        print("Hello %s, Welcome to the Jungle" % self.visitorName)

class RateJungle:
    def __init__(self, feedback):
        # feedback (1-10) :  1 is the best.
        self.feedback = feedback # Public Attribute

        # Public attribute with single underscore sign
        # Single _ signifies that author does not want to acecess it directly
        self._staffRating = 50

        self.__jungleGuideRating = 100 # Private Attribute

        self.updateStaffRating() # update Staff rating based on feedback
        self.updateGuideRating() # update Guide rating based on feedback

    def printRating(self):
        print("Feedback : %s \tGuide Rating: %s \tStaff Rating: %s "
            % (self.feedback, self.__jungleGuideRating, self._staffRating))

    def updateStaffRating(self):
        """ update Staff rating based on visitor feedback"""
        if self.feedback < 5 :
            self._staffRating += 5
        else:
            self._staffRating -= 5

    def updateGuideRating(self):
        """ update Guide rating based on visitor feedback"""
        if self.feedback < 5 :
            self.__jungleGuideRating += 10
        else:
            self.__jungleGuideRating -= 10
Listing 1.40 Accessing public and private attributes
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#main.py

# import class 'Jungle' and 'RateJungle' from jungleBook.py
from jungleBook import Jungle, RateJungle

def main():
    ## create object of class Jungle
    j = Jungle("Meher")
    j.welcomeMessage() # Hello Meher, Welcome to the Jungle

    r = RateJungle(3)
    r.printRating() # Feedback : 3  Guide Rating: 110  Staff Rating: 55

    # _staffRating can be accessed "directly", but not a good practice.
    # Use the method which is provided by the author
    # e.g. below is the bad practice
    r._staffRating = 30 # directly change the _staffRating
    print("Staff rating : ", r._staffRating) # Staff rating :  30

    ## access to private attribute is not allowed
    ## uncomment following line to see error
    # print("Jungle Guide rating : ", r.__jungleGuideRating)

    ## private attribute can still be accessed as below,
    ## objectName._className.__attributeName
    print ("Guide rating : ",  r._RateJungle__jungleGuideRating) # Guide rating :  110

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.7. Class Attribute

Class attribute is the variable of the class (not of method) as shown in Line 7 of Listing 1.41. This attribute can be available to all the classes without any inheritance e.g. At Line 44, the class Test (Line 41) is using the class-attribute ‘sum_of_feedback’ of class Jungle (Line 7). Note that, we need to use the class name to access the class attribute e.g. Jungle.sum_of_feedback (Lines 30 and 44).

Listing 1.41 Class attributes and it’s access
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#jungleBook.py

#class declaration
class Jungle:
    # class attribute
    # use __sum_of_feedback to hide it from the child class
    sum_of_feedback = 0.0

    #constructor with default values
    def __init__(self, name="Unknown"):
        self._visitorName = name # please do not access directly

    def welcomeMessage(self):
        print("Hello %s, Welcome to the Jungle" % self.visitorName)

    def averageFeedback(self):
        #average feedback is hided for the the child class
        self.__avg_feedback = Jungle.sum_of_feedback/RateJungle.total_num_feedback
        print("Average feedback : ", self.__avg_feedback)

class RateJungle(Jungle):
    # class attribute
    total_num_feedback = 0

    def __init__(self, name, feedback):
        # feedback (1-10) :  1 is the best.
        self.feedback = feedback # Public Attribute

        # add new feedback value to sum_of_feedback
        Jungle.sum_of_feedback += self.feedback
        # increase total number of feedback by 1
        RateJungle.total_num_feedback += 1

        # inheriting the constructor of the class
        super().__init__(name)

    # using parent class attribute i.e. visitorName
    def printRating(self):
        print("Thanks %s for your feedback" % self._visitorName)

class Test:
    def __init__(self):
        # inheritance is not required for accessing class attribute
        print("sum_of_feedback (Jungle class attribute) : ", Jungle.sum_of_feedback)
        print("total_num_feedback (RateJungle class attribute) : ", RateJungle.total_num_feedback)
Listing 1.42 Main program
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#main.py

## import class 'Jungle', 'RateJungle' and 'Test' from jungleBook.py
from jungleBook import Jungle, RateJungle, Test

def main():
    r = RateJungle("Meher", 3)
    s = RateJungle("Krishna", 2)

    r.averageFeedback() # Average feedback :  2.5


    # Test class is using other class attributes without inheritance
    w = Test()
    ''' sum_of_feedback (Jungle class attribute) :  5.0
        total_num_feedback (RateJungle class attribute) :  2
    '''

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.8. Special methods

There are some special method, which are invoked under certain cases e.g. __init__ method is invoked, when an object of the instance is created. In this section, we will see some more special methods.

1.9.8.1. __init__ and __del__

The __init__ method is invoked when object is created; whereas __del__ is always invoked at the end of the code; e.g. we invoke the ‘del’ at Line 21 of Listing 1.43, which deletes object ‘s1’ and remaining objects are printed by Line 13. But, after Line 25, there is no further statement, therefore the ‘del’ command will automatically executed, and results at Lines 31-32 will be displayed. The ‘del’ command is also known as ‘destructor’.

Listing 1.43 __init__ and __del__ function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# delEx.py

class Student:
    totalStudent = 0

    def __init__(self, name):
        self.name = name
        Student.totalStudent += 1
        print("Total Students (init) : ", self.totalStudent)

    def __del__(self):
        Student.totalStudent -= 1
        print("Total Students (del) : ", self.totalStudent)

def main():
    s1 = Student("Meher") # Total Students (init) :  1
    s2 = Student("Krishna") # Total Students (init) :  2
    s3 = Student("Patel") # Total Students (init) :  3

    ## delete object s1
    del s1 # Total Students (del) :  2

    # print(s1.name) # error because s1 object is deleted
    print(s2.name) # Krishna
    print(s3.name) # Patel

    ## since there is no further statements, therefore
    ## 'del' will be executed for all the objects and
    ## following results will be displayed

    # Total Students (del) :  1
    # Total Students (del) :  0

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.8.2. __str__

When __str__ is defined in the class, then ‘print’ statement for object (e.g. print(j) at Line 11 of Listing 1.44), will execute the __str__ statement, instead of printing the address of object, as happened in Listing 1.26. This statement is very useful for providing the useful information about the class using print statement.

Listing 1.44 __str__ method is executed when ‘print’ statement is used for object
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#strEx.py

#class declaration
class Jungle:
    def __str__(self):
        return("It is an object of class Jungle")

def main():
    # create object of class Jungle
    j = Jungle()
    print(j)  # It is an object of class Jungle

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.8.3. __call__

The __call__ method is executed, when object is used as function, as shown in Line 20 of Listing 1.45; where object ‘d’ is used as function i.e. d(300).

Listing 1.45 __call__ method is executed when object is used as function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# callEx.py

#class declaration
class CalculatePrice:
    # discount in %
    def __init__(self, discount):
        self.discount = discount

    def __call__(self, price):
        discountPrice = price - price*self.discount/100
        return (price, discountPrice)

def main():
    # create object of class CalculatePrice with 10% discount
    d = CalculatePrice(10)

    # using object as function i.e. d(300)
    # since two variables are return by call fuction, therefore
    # unpack the return values in two variables
    price, priceAfterDiscount =  d(300)
    print("Original Price: %s,  Price after discount : %s "
                    % (price, priceAfterDiscount))

    ## or use below method, if you do not want to unpack the return values
    # getPrices =  d(300)
    # print("Original Price: %s,  Price after discount : %s "
    #                 % (getPrices[0], getPrices[1]))

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.8.4. __dict__ and __doc__

__dict__ is used to get the useful information about the class (Line 21); whereas __doc__ prints the docstring of the class (Line 30).

Listing 1.46 __dict__ and __doc__
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#dictEx.py

#class declaration
class Jungle:
    """ List of animal and pet information
        animal = string
        isPet = string
    """
    def __init__(self, animal="Elephant", isPet="yes"):
        self.animal =  animal
        self.isPet = isPet

def main():
    # create object of class Jungle
    j1 = Jungle()
    print(j1.__dict__) # {'isPet': 'yes', 'animal': 'Elephant'}

    j2 = Jungle("Lion", "No")
    print(j2.__dict__) # {'isPet': 'No', 'animal': 'Lion'}

    print(Jungle.__dict__)
    """ {'__doc__': '__doc__': ' List of animal and pet information \n
                        animal = string\n      isPet = string\n    ',
        '__weakref__': <attribute '__weakref__' of 'Jungle' objects>,
        '__module__': '__main__',
        '__dict__': <attribute '__dict__' of 'Jungle' objects>,
        '__init__': <function Jungle.__init__ at 0x00466738>}
    """

    print(Jungle.__doc__)
    """List of animal and pet information
        animal = string
        isPet = string
    """

# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.9.8.5. __setattr__ and __getattr__

Method __setattr__ is executed, whenever we set the value of an attribute. __setattr__ can be useful for validating the input-types before assigning them to attributes as shown in Line 9 of Listing 1.47. Please read the comments of Listing 1.47 for better understanding. Similarly, __getattr__ is invoked whenever we try to access the value of an attribute, which is not in the dictionary.

Listing 1.47 __setattr__ and __getattr__
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# setAttr.py
class StudentID:
    def __init__(self, id, name, age = "30"):
        self.id = id
        self.firstName = name
        self.age = age

    # all the init parameters need to be specified in 'setattr'
    def __setattr__(self, name, value):
        if(name == "id"): # setting id
            if isinstance(value, int) and value > 0 :
                self.__dict__["id"] = value
            else:
                # print("Id must be positive integer")
                raise TypeError("Id must be positive integer")
        elif (name == "firstName"): # setting firstName
            self.__dict__["firstName"] = value
        else: # setting age
            self.__dict__[name] = value

    # getattr is executed, when attribute is not found in dictionary
    def __getattr__(self, name):
        raise AttributeError("Attribute does not exist")

def main():
    s1 = StudentID(1, "Meher")
    print(s1.id, s1.firstName, s1.age) # 1 Meher 30

    ## uncomment below line to see the "TypeError" generated by 'setattr'
    # s2 = StudentID(-1, "Krishna", 28)
    """
    Traceback (most recent call last):
    [...]
    raise TypeError("Id must be positive integer")
    """

    s3 = StudentID(1, "Krishna", 28)
    print(s3.id, s3.firstName, s3.age) # 1 Krishna 28

    ## uncomment below line to see the "AttributeError" generated by 'getattr'
    # print(s3.lastName) # following message will be displayed
    """ Traceback (most recent call last):
        [...]
        AttributeError: Attribute does not exist
    """
# standard boilerplate to set 'main' as starting function
if __name__=='__main__':
    main()

1.10. Conclusion

In this chapter, we learn various features of Python along with object oriented programming. Also, we learn some of the good coding practices in Python. Further, We saw that there is no concept of private attributes in Python. Lastly, we discuss various special methods available in Python which can enhance the debugging and error checking capability of the code. We will see all these features in details in the subsequent chapters.