Foundations of programming

Lecture nine - Python - introduction

Work Plan

  1. Introduction, types of programming languages, pseudo-code
  2. Variables, data types
  3. Pointers and arrays
  4. Simple instructions and tricks
  5. Functions and procedures
  6. Debug
  7. Input / Output
  8. Data structures
  9. Modern C++
  10. Python
  11. Python cont.
  12. Functional / object oriented programming
  13. Software development process, git, agile, tdd
  14. Final Test during last classes: about 30 open/close questions - mostly closed.

Modules

Modules

image

Modules

Modular design means that a complex system is broken down into smaller parts or components, i.e. modules. There is hardly any product nowadays, which doesn't heavily rely on modularisation, like cars, mobile phones.

These components can be independently created and tested.

In many cases, they can be even used in other systems as well.

Modules

If you import a module in Python using "import abc" the interpreter searches for this module in the following locations and in the order given:
The directory of the top-level file, i.e. the file being executed. The directories of PYTHONPATH, if this global variable is set. Standard installation path Linux/Unix e.g. in /usr/lib/python3.5.

Modules

A module in Python is just a file containing Python definitions and statements.

The module name is moulded out of the file name by removing the suffix .py. For example, if the file name is fibonacci.py, the module name is fibonacci.

Example, save code in file named fibonnaci.py

def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return f

Execution:

import fibonacci

fibonacci.fib(7)

Modules

Usually, modules contain functions or classes, but there can be "plain" statements in them as well.

These statements can be used to initialize the module. They are only executed when the module is imported.

Save a module as single.py with a single line

print("The module is imported now!")

We save with the name "one_time.py" and import it two times in the interpreter session:

>>> import single
The module is imported now!
>>> import single
>>>

We can see that it was only imported once. Each module can only be imported once per interpreter session or in a program or script.

Modules

Essentially a Python module is a script, so it can be run as a script:

python single.py 
There are different kind of modules:
Those written in Python - They have the suffix: .py Dynamically linked C modules - Suffixes are: .dll, .pyd, .so, .sl, ... C-Modules linked with the Interpreter

It's possible to get a complete list of third type modules:

import sys
print(sys.builtin_module_names)

Immutable

Immutable once more

image

Lists

Lists in Python can have various data types:

group = ["Bob", 23, "George", 72, "Myriam", 29]

Lists can have sublists as elements. These sublists may contain sublists as well, i.e. lists can be recursively constructed by sublist structures.

>>> complex_list = [["a",["b",["c","x"]]]]
>>> complex_list = [["a",["b",["c","x"]]],42]
>>> complex_list[0][1]
['b', ['c', 'x']]
>>> complex_list[0][1][1][0]
'c'

Slicing the list

Slicing works with up to three arguments. If the third argument is for example 3, only every third element of the list, string or tuple from the range of the first two arguments will be taken.

General form:

s[begin: end: step]

The resulting sequence consists of the following elements:

s[begin], s[begin + 1 * step], ... s[begin + i * step] for all (begin + i * step) < end.

Example:

>>> str = "Python under Linux is great"
>>> str[::3]
'Ph d n  e'

Immutable again

As we discussed before in Python we have immutable types (simple types) and mutable (list,dict,set,bytearray,user-defined classes).

string_build = ""
for data in container:
    string_build += str(data)

Because strings are immutable, concatenating two strings together actually creates a third string which is the combination of the previous two.

Mutable

We can use the list to obtain the same result.

builder_list = []
for data in container:
    builder_list.append(str(data))
"".join(builder_list)

This code takes advantage of the mutability of a single list object to gather your data together and then allocate a single result string to put your data in.

Copies of objects

Assigments in python :

>>> colours1 = ["red", "green"]
>>> colours2 = colours1
>>> colours2[1] = "blue"
>>> colours1
['red', 'blue']

Assignment statements in Python do not copy objects, they create bindings between a target and an object.

Copies of objects

It's possible to do shallow list copy

>>> list1 = ['a','b','c','d']
>>> list2 = list1[:]
>>> list2[1] = 'x'
>>> print list2
['a', 'x', 'c', 'd']
>>> print list1
['a', 'b', 'c', 'd']

it is shallow because for example this does not work for sublists:

>>> lst1 = ['a','b',['ab','ba']]
>>> lst2 = lst1[:]
>>> lst2[0] = 'c'
>>> lst2[2][1] = 'd'
>>> print(lst1)
['a', 'b', ['ab', 'd']]

Shallow and deep copy

In order to make deep and shallow copies you should use module copy.
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original. A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.

Input:

from copy import *

lst1 = ['a','b',['ab','ba']]

lst2 = deepcopy(lst1)
lst3 = copy(lst1)

lst2[2][1] = "d2"
lst3[2][1] = "d3"
lst2[0] = "c2";
lst3[0] = "c3";

print lst1
print lst2
print lst3

Output:

['a', 'b', ['ab', 'd3']]
['c2', 'b', ['ab', 'd2']]
['c3', 'b', ['ab', 'd3']]

Stuff to deal with

Another pitfall related to mutability is the following scenario:

def my_function(param=[]):
    param.append("thing")
    return param

my_function() # returns ["thing"]
my_function() # returns ["thing", "thing"]

What you might think would happen is that by giving an empty list as a default value to param, a new empty list is allocated each time the function is called and no list is passed in. But what actually happens is that every call that uses the default list will be using the same list.

This is because Python (a) only evaluates functions definitions once, (b) evaluates default arguments as part of the function definition, and (c) allocates one mutable list for every call of that function.

Not pretty workaround:

def my_function2(param=None):
    if param is None:
        param = []
    param.append("thing")
    return param

Data Structures

Data Structures

image

Dictionary

Dictionary is a map implementation in Python.

The update method merges the keys and values of one dictionary into another, overwriting values of the same key:

>>> w={"house":"Haus","cat":"Katze","red":"rot"}
>>> w1 = {"red":"rouge","blau":"bleu"}
>>> w.update(w1)
>>> print w
{'house': 'Haus', 'blau': 'bleu', 'red': 'rouge', 'cat': 'Katze'}

Dictionary

No method is needed to iterate over a dictionary:

for key in d:
    print key

But it's possible to use the method iterkeys():

for key in d.iterkeys():
    print key

The method itervalues() is a convenient way for iterating directly over the values:

for val in d.itervalues():
    print val

List from Dictionaries

It's possible to create lists from dictionaries by using the methods items(), keys() and values().

>>> w={"house":"Haus","cat":"Katze","red":"rot"}
>>> w.items()
[('house', 'Haus'), ('red', 'rot'), ('cat', 'Katze')]
>>> w.keys()
['house', 'red', 'cat']
>>> w.values()
['Haus', 'rot', 'Katze']    

Dictionaries from List

We have two lists, one containing the dishes and the other one the corresponding countries:

>>> dishes = ["pizza", "sauerkraut", "paella", "Hamburger"]
>>> countries = ["Italy", "Germany", "Spain", "USA"]

Now we will create a dictionary, which assigns a dish to a country. For this purpose we need the function zip().

>>> country_specialities = zip(countries, dishes)
>>> print country_specialities
[('Italy', 'pizza'), ('Germany', 'sauerkraut'), ('Spain', 'paella'), ('USA', 'Hamburger')]

The variable country_specialities contains now the "dictionary" in the 2-tuple list form. This form can be easily transformed into a real dictionary with the function dict().

>>> country_specialities_dict = dict(country_specialities)
>>> print country_specialities_dict
{'Germany': 'sauerkraut', 'Spain': 'paella', 'Italy': 'pizza', 'USA': 'Hamburger'}

Set

The data type "set", which is a collection type, has been part of Python since version 2.4. A set contains an unordered collection of unique and immutable objects.

If we want to create a set, we can call the built-in set function with a sequence or another iterable object.

>>> x = set("A Python Tutorial")
>>> x
set(['A', ' ', 'i', 'h', 'l', 'o', 'n', 'P', 'r', 'u', 't', 'a', 'y', 'T'])
>>> type(x)
<type 'set'>

We can define sets (since Python2.6) without using the built-in set function. We can use curly braces instead:

x = {"A","b","C"}
print(x)
y = {"Abc"}
print(y)
z = set(["asd","ads"])
print(z)
{'A', 'b', 'C'}
{'Abc'}
{'asd', 'ads'}

Set

Sets are implemented in a way, which doesn't allow mutable objects. The following example demonstrates that we cannot include for example lists as elements:

>>> cities = set((("Python","Perl"), ("Paris", "Berlin", "London")))
>>> cities = set((["Python","Perl"], ["Paris", "Berlin", "London"]))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Set Operations:
add(element) clear() copy() difference(set) discard(element) remove(element) intersection(s) issubset() issuperset() pop() / remove an element of the sets

Matrix

Matrix is a two-dimensional table. In python, matrix is a nested list. A list is created by placing all the items (elements) inside a square bracket [ ], separated by commas.

# a is 2-D matrix with integers
a = [['Roy',80,75,85,90,95],
    ['John',75,80,75,85,100],
    ['Dave',80,80,80,90,95]]

#b is a nested list but not a matrix
b= [['Roy',80,75,85,90,95],
    ['John',75,80,75],
    ['Dave',80,80,80,90,95]]

Create matrix in a loop

A possible way: you can create a matrix of n*m elements by first creating a list of n elements (say, of n zeros) and then make each of the elements a link to another one-dimensional list of m elements:

n = 3
m = 4
a = [0] * n
for i in range(n):
    a[i] = [0] * m
print(a)

Similar to list we can access elements of a matrix by using square brackets [] after the variable like a[row][col].

Negative indexing

Python allows negative indexing for sequences.

Example:

lst1 = ['Ajay', 'Bobby','Ashok', 'Vijay', 'Anil', 'Rahul','Alex', 'Christopher']
print(lst1[-1])
print(lst1[-2])
print(lst1[-3])

Output:

Christopher
Alex
Rahul

Comparing sequences

Sequence objects may be compared to other objects with the same sequence type.

The comparison uses lexicographical ordering: first the first two items are compared, and if they differ this determines the outcome of the comparison; if they are equal, the next two items are compared, and so on, until either sequence is exhausted.

If two items to be compared are themselves sequences of the same type, the lexicographical comparison is carried out recursively. If all items of two sequences compare equal, the sequences are considered equal.

If one sequence is an initial sub-sequence of the other, the shorter sequence is the smaller (lesser) one.

Examples:

(1, 2, 3)              < (1, 2, 4)
[1, 2, 3]              < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4)           < (1, 2, 4)
(1, 2)                 < (1, 2, -1)
(1, 2, 3)             == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

Functions

Functions

image

Call by value/references

Python uses a mechanism, which is known as "Call-by-Object", sometimes also called "Call by Object Reference" or "Call by Sharing".

If we pass a list to a function, we have to consider two cases: Elements of a list can be changed in place, i.e. the list will be changed even in the caller's scope. If a new list is assigned to the name, the old list will not be affected, i.e. the list in the caller's scope will remain untouched.

def func1(list):
    print list
    list = [47,11]
    print list
tab = [0,1,2]
func1(fib)
print(fib)

Output :

[0, 1, 2]
[47, 11]

[0, 1, 2]

Call by value/references

This changes drastically, if we include something in the list by using +=. To show this, we have a different function func2() in the following example:

def func2(list):
    print list
    list += [47,11]
    print list

tab = [0,1,2]
func1(tab)
print(tab)

Output :

[0, 1, 2]
[0, 1, 2, 47, 11]

[0, 1, 2, 47, 11]

Functions

Remainder: function names are references to functions and in Python we can assign multiple names to the same function.

In pyhton we can define functions inside of a function:

def f():

    def g():
        print("Hi, it's me 'g'")
        print("Thanks for calling me")

    print("This is the function 'f'")
    print("I am calling 'g' now:")
    g()


f()

Output:

This is the function 'f'
I am calling 'g' now:
Hi, it's me 'g'
Thanks for calling me

Functions as parameters

Due to the fact that every parameter of a function is a reference to an object and functions are objects as well, we can pass functions - or better "references to functions" - as parameters to a function.

def g():
    print("Hi, it's me 'g'")
    print("Thanks for calling me")

def f(func):
    print("Hi, it's me 'f'")
    print("I will call 'func' now")
    func()

f(g)

Output:

Hi, it's me 'f'
I will call 'func' now
Hi, it's me 'g'
Thanks for calling me

Closures

A function defined inside another function is called a nested function.

def print_msg(msg):
# This is the outer enclosing function

    def printer():
    # This is the nested function
        print(msg)

    return printer  

another = print_msg("Hello")
another()

The print_msg() function was called with the string "Hello" and the returned function was bound to the name another. On calling another(), the message was still remembered although we had already finished executing the print_msg() function.

This technique by which some data ("Hello") gets attached to the code is called closure in Python.

Closure

As seen from the above example, we have a closure in Python when a nested function references a value in its enclosing scope.

The criteria that must be met to create closure in Python are summarized in the following points.
We must have a nested function (function inside a function). The nested function must refer to a value defined in the enclosing function. The enclosing function must return the nested function.

Closures can avoid the use of global values and provides some form of data hiding.

More practical?

Encode polynomial of the form a*x^2 + b*x + c in a function.

def polynomial_creator(a, b, c):
    def polynomial(x):
        return a * x**2 + b * x + c
    return polynomial

p1 = polynomial_creator(2, 3, -1)
p2 = polynomial_creator(-1, 2, 1)

for x in range(-2, 2, 1):
    print(x, p1(x), p2(x))

Output:

-2 1 -7
-1 -2 -2
0 -1 1
1 4 2

Decorators

A decorator in Python is any callable Python object that is used to modify a function or a class. A reference to a function "func" or a class "C" is passed to a decorator and the decorator returns a modified function or class.

def our_decorator(func):
    def function_wrapper(x):
        print("Before calling " + func.__name__)
        func(x)
        print("After calling " + func.__name__)
    return function_wrapper

def foo(x):
    print("Hi, foo has been called with " + str(x))

print("We call foo before decoration:")
foo("Hi")

print("We now decorate foo with f:")
foo = our_decorator(foo)

print("We call foo after decoration:")
foo(42)

After the decoration "foo = our_decorator(foo)", foo is a reference to the 'function_wrapper'. 'foo' will be called inside of 'function_wrapper', but before and after the call some additional code will be executed, i.e. in our case two print functions.

Output:

We call foo before decoration:
Hi, foo has been called with Hi
We now decorate foo with f:
We call foo after decoration:
Before calling foo
Hi, foo has been called with 42
After calling foo

Decorator definition

Proper decorator definition occurrs in the line before the function header. The "@" is followed by the decorator function name.

def our_decorator(func):
    def function_wrapper(x):
        print("Before calling " + func.__name__)
        func(x)
        print("After calling " + func.__name__)
    return function_wrapper

@our_decorator
def foo(x):
    print("Hi, foo has been called with " + str(x))

foo("Hi")

We can decorate every other function which takes one parameter with the decorator our_decorator.

Decorators

Summarizing we can say that a decorator in Python is a callable Python object that is used to modify a function, method or class definition. The original object, the one which is going to be modified, is passed to a decorator as an argument.

The decorator returns a modified object, e.g. a modified function, which is bound to the name used in the definition.

Decorators dynamically alter the functionality of a function, method or class without having to directly use subclasses. This is ideal when you need to extend the functionality of functions that you don't want to modify and control it execution.

List of Python decorators: https://wiki.python.org/moin/PythonDecoratorLibrary

Exceptions

Exceptions

image

Exceptions

An exception is an event which occurs during the execution of a program and disrupts the normal flow of the program’s instructions.

Like many languages, in Python, a try statement is available for exception handling.

try:
    You do your operations here;
    ......................
except ExceptionI:
    If there is ExceptionI, then execute this block.
except ExceptionII:
    If there is ExceptionII, then execute this block.
    ......................
else:
    If there is no exception then execute this block.

A single try statement can have multiple except statements depending on the requirement. In this case, a try block contains statements that can throw different types of exceptions.

We can also add a generic except clause which can handle all possible types of exceptions.

Exceptions

With try block, we also have the option to define the “finally” block. This clause allows defining statements that we want to execute, no matters whether the try block has raised an exception or not.

try:
    You do your operations here;
    ......................
    Due to any exception, this may be skipped.
finally:
    This would always be executed.
    ......................

Example

Example:

try:
    x = float(input("Your number: "))
    inverse = 1.0 / x
except ValueError:
    print("You should have given either an int or a float")
except ZeroDivisionError:
    print("Infinity")
except:
    print("Unexpected error:", sys.exc_info()[0])
finally:
    print("There may or may not have been an exception.")

Raise Exception

We can forcefully raise an exception using the raise keyword. We can also optionally pass values to the exception and specify why this exception should occur. Here is the syntax for calling the “raise” method.

raise [Exception [, args [, traceback]]]

where,

The exception is the name of the exception. The “args” is optional and represents the value of the exception argument. The final argument, traceback, is also optional and if present, is the traceback object used for the exception.

Regular exp

Regular Expressions

image

Regular exp

The term "regular expression", sometimes also called regex or regexp, is originated in theoretical computer science.

A regular expression is a special text string for describing a search pattern.

Regular Expressions are used in programming languages to filter texts or textstrings. It's possible to check, if a text or a string matches a regular expression. A great thing about regular expressions: The syntax of regular expressions is the same for all programming and script languages, e.g. Python, Perl, Java, SED, AWK and even X#.

Regexp

If you want to use regular expressions in Python, you have to import the re module, which provides methods and functions to deal with regular expressions.

Regular expressions in Python are represented as normal strings.

Example:

"cat" 

"^[\w\.\+\-]+\@[\w]+\.[a-z]{2,3}$"

First one match every sunstring of the form "cat", second one looks for e-mail address.

Example

Method re.search(expr,s) checks a string s for an occurrence of a substring which matches the regular expression expr.

>>> import re
>>> x = re.search("cat","A cat and a rat can't be friends.")
>>> print(x)
<_sre.SRE_Match object at 0x7fd4bf238238>
>>> x = re.search("cow","A cat and a rat can't be friends.")
>>> print(x)
None

If a match has been possible, we get a so-called match object as a result, otherwise the value will be None.

Regular Expressions symbols

. - any single symbol [] - character class, example [xvd] * - any number of elements from class (even zero), example [0-9]* + - any number of elements from class (but at least one), example [0-9]+ ? - A question mark declares that the preceding character or expression is optional.
d - Matches any decimal digit; equivalent to the set [0-9]. D - The complement of d. It matches any non-digit character; equivalent to the set [^0-9]. s - Matches any whitespace character; equivalent to [ tnrfv]. S - The complement of s. It matches any non-whitespace character; equiv. to [^ tnrfv]. w - Matches any alphanumeric character; equivalent to [a-zA-Z0-9]. With LOCALE, it will match the set [a-zA-Z0-9] plus characters defined as letters for the current locale. W - Matches the complement of w. b - Matches the empty string, but only at the start or end of a word. B - Matches the empty string, but not at the start or end of a word.

Matched object

A match object contains the methods group(), span(), start() and end().
span() returns a tuple with the start and end position. group(), if called without argument, returns the substring, which had been matched by the complete regular expression. The methods start() and end() are in a way superfluous as the information is contained in span(), i.e. span()[0] is equal to start() and span()[1] is equal to end()

Example:

>>> import re
>>> mo = re.search("[0-9]+", "Customer number: 232454, Date: February 12, 2011")
>>> mo.group()
'232454'
>>> mo.span()
(17, 23)
>>> mo.start()
17
>>> mo.end()
23
>>> mo.span()[0]
17
>>> mo.span()[1]
23

All matched

To get all the substrings in a string, which match a regular expression we use findall method of the re module:

re.findall(pattern, string[, flags])

Example:

>>> t="A fat cat doesn't eat oat but a rat eats bats."
>>> mo = re.findall("[force]at", t)
>>> print(mo)
['fat', 'cat', 'eat', 'oat', 'rat', 'eat']

References

https://www.python-course.eu/python3_re.php

https://codehabitude.com/2013/12/24/python-objects-mutable-vs-immutable/

https://www.python-course.eu/python3_dictionaries.php

https://www.python-course.eu/python3_deep_copy.php

https://www.python-course.eu/python3_sequential_data_types.php

https://www.python-course.eu/python3_sets_frozensets.php

https://www.python-course.eu/python3_decorators.php

https://www.programiz.com/python-programming/closure

https://www.programiz.com/python-programming/matrix

http://www.techbeamers.com/python-try-except-beginners/

https://www.python-course.eu/python3_modules_and_modular_programming.php

https://docs.python.org/3/tutorial/datastructures.html#looping-techniques