138
Python programming - Lecture Notes - Dr. Elad Aigner-Horev Contents 1. Introduction 3 2. Conditional control structures 4 3. Loop structures 5 3.1. Scopes of for loops .................................... 8 4. Functions 9 4.1. Scoping ........................................... 10 4.2. Passing arguments to functions .............................. 11 4.3. Returning values from functions ............................. 12 4.4. Default values ........................................ 12 5. Modules 13 5.1. Packages .......................................... 14 5.2. Namespaces and scopes .................................. 15 6. Basic data types 15 6.1. Creating sequences ..................................... 16 6.2. Sequence indexing ..................................... 17 6.3. List comprehensions .................................... 20 6.4. Non homogenous data types ................................ 23 6.5. List manipulation ..................................... 24 6.5.1. A word of caution ..................................... 27 6.6. Comparing lists ....................................... 27 6.7. Sorting lists ......................................... 28 6.7.1. sort() and sorted() ................................. 30 6.7.2. reverse() and reversed() ............................. 30 1

 · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Python programming

- Lecture Notes -

Dr. Elad Aigner-Horev

Contents1. Introduction 3

2. Conditional control structures 4

3. Loop structures 53.1. Scopes of for loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

4. Functions 94.1. Scoping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104.2. Passing arguments to functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114.3. Returning values from functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124.4. Default values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

5. Modules 135.1. Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145.2. Namespaces and scopes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

6. Basic data types 156.1. Creating sequences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166.2. Sequence indexing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176.3. List comprehensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206.4. Non homogenous data types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236.5. List manipulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246.5.1. A word of caution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276.6. Comparing lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276.7. Sorting lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286.7.1. sort() and sorted() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306.7.2. reverse() and reversed() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

1

Page 2:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

6.8. The del statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306.9. A few additional examples for lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316.10. Methods for set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316.11. Functions revisited . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

7. Dictionaries 337.1. Views into dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367.2. The map() built-in function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

8. Built-in functions for "conversions" 38

9. Object oriented programming 389.1. Private and public members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429.2. Destructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449.3. Inheritence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479.4. The diamond problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 529.5. Abstract classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 539.6. Class attributes and inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 549.7. Bound and unbound methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559.8. Static methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569.9. Class methods and variables having the same names . . . . . . . . . . . . . . . . . . 589.10. Static variables for functions and methods . . . . . . . . . . . . . . . . . . . . . . . . 59

10. Exceptions 61

11. Iterators and generators 63

12. Custom arrays 6612.1. Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

13. Multithreading in Python 6913.1. Creating threads in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7013.2. GIL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7213.3. Atomic operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7513.4. Locks and re-entrant locks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7713.4.1.Locks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7713.4.2.Reentrant locks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8013.5. Condition variables and events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8113.5.1.Condition variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8113.5.2.Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8413.6. Some subtleties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8613.7. Semaphores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

2

Page 3:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

13.8. The dining philosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9313.9. Thread barriers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9613.10.Custom thread pool (that does not work) . . . . . . . . . . . . . . . . . . . . . . . . 9813.11.The with statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

14. Exercises with solutions 102

§1 Introduction

In what follows a brief overview of the Python programming language is provided. We shallstart with an inspection of the main data types supported by Python and how to manipulatethese, followed by object oriented programming in Python, and then consider some of the coremultithreading capabilities provided by Python.

Python is a general-purpose high-level programming language; with one of its core features isthat is much more concise than languages such as C/C++ and Java. Indeed, its syntax allowsprogrammers to express fairly complicated concepts in fewer lines of code than would be possiblein the aforementioned languages. Python supports multiple programming paradigms, includingobject-oriented, imperative and functional programming or procedural styles and has a large andcomprehensive library of functions and classes extending its core functionality.

To execute Python programs we must first invoke the Python interpreter. Python interpreters areavailable for installation on many operating systems, allowing Python code execution on a majorityof systems. Python was conceived in the late 1980s by Guido van Rossum

Throughout, the Python code snippets shown in these lectures are compliant with Python 3.4.1.We shall make no attempt to account for previous versions of Python. One should mind that Pythonis constantly being developed and so, for instance, there are serious differences between Python 2.xversions and Python 3.x versions.

As already mentioned, to execute Python programs we must first invoke the Python inter-preter. For Mac machines one simply types python3 at the terminal which will in turn invokethe python interpreter. To execute a .py (i.e., a Python file) is done by simply writing python3path/to/file/filename.py in the terminal.

For Windows machines, the Python installation is usually placed in C://Python34 (in case ofPython 3.4). To invoke the Python interpreter from the DOS command line set first the environmentvariable path to include this Python installation directory (a task that should be done automaticallyafter installing IDLE) with set path=%path%;C:/python34 at the DOS command line. OnWindows Python files usually have the suffix *.pyw.

Another alternative is to directly work with IDLE application. Once this is running we may usethe menu of the program to load and run files. There are of course prompt commands one could useto avoid using the interface of IDLE, we shall not address these.

The Python interpreter, once invoked, will present the following prompt.1 Python 3.4.1 (v3.4.1:c0e311e010fc, May 18 2014, 00:54:21)2 [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin

3

Page 4:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

3 Type "copyright", "credits" or "license()" for more information.45 >>> print("Hello from the Python prompt")6 Hello from the Python prompt7 >>>

If we choose to write a *.py file, then the file would look as follows.1 #!/usr/bin/env python323 print ("Hello from a Python file!"),4 print ("Welcome to Python!")

The execution (on Mac) would look as follows.1 $ python3 ~/Desktop/Python/Hello.py2 Hello from a Python file! Welcome to Python!

The comma separating the two print() commands in this file instructs Python to no start a newline.

1 #!/usr/bin/env python323 x = 54 y = 2.55 print (x ** y) # exponentiation6 ---------------------------------------7 Output:89 55.90169943749474

Note here that unlike C/C++ and Java the type of x and y has not been predefined.The power of Python is perhaps best demonstrated through the following.

1 x = 12 y = "abc"34 x ,y = y, x56 print (x, y)7 ----------------------------8 Output:9

10 abc 1

This is all it takes to swap variables in Python. Notice that the type of x and y was not predefined;unlike the situation in C/C++ and Java.

§2 Conditional control structures

Python uses indentation to distinguish between sections of code. Other programming languagesoften use braces for this purpose. The Python programmer chooses the number of spaces to indenta block, and the number of spaces must remain consistent for each statement in the block. Pythonrecognises new blocks when there is a change in the number of indented spaces.

The syntax for the if/else clause is the following.

4

Page 5:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 #!/usr/bin/env python323 x = int(input("Enter an integer: "))4 if x <= 10:5 print ("at most 10")6 else:7 if x <= 20:8 print ("between 10 and 20")9 else:

10 print ("above 20")

The function input() returns a string which is then converted into an integer by int().An alternative form to the above code which resembles switch clauses found in, say C/C++

and Java, in spirit is as follows.1 #!/usr/bin/env python323 x = int(input("Enter an integer: "))4 if x <= 10:5 print ("at most 10")6 elif x <= 20:7 print ("between 10 and 20")8 else:9 print ("above 20")

§3 Loop structures

The while loop keeps iterating as long as a boolean expression keeps evaluating to true.1 #!/usr/bin/env python323 x = 14 d = 55 i=06 while x < 30:7 print (x)8 x = x+i*d9 i = i +1

To have a while loop iterate forever one could use while True: or while <Some Number>as long as the number is not zero.

1 #!/usr/bin/env python323 while 1:4 x = int(input(">> "))5 if x < 0:6 break7 else:8 print (x)

The while condition can be rather complicated.1 a = int(input("a = "))2 b = int(input("b = "))3 c = int(input("c = "))4 while a< b==c:

5

Page 6:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

5 print("condition satisfied")6 a = int(input("a = "))7 b = int(input("b = "))8 c = int(input("c = "))

The Boolean logical operators and and or are so-called short-circuit operators: their argumentsare evaluated from left to right, and evaluation stops as soon as the outcome is determined. Forexample, if A and C are true but B is false, A and B and C does not evaluate the expression C.When used as a general value and not as a Boolean, the return value of a short-circuit operator isthe last evaluated argument.

It is possible to assign the result of a comparison or other Boolean expression to a variable.1 >>> string1, string2, string3 = "", ’Trondheim’, ’Hammer Dance’2 >>> non_null = string1 or string2 or string33 >>> non_null # note that string2 is the value of non_null4 ’Trondheim’

An additional logical operator of Python is not.The for loop structure handles counting based iterations.

1 #!/usr/bin/env python323 for i in range (60,80):4 print (i/2),5 print ("\n")6 for i in range (10):7 print (i+1), # use comma to force printing in the same line8 print ("\n")9 for i in range (1,10,4):

10 print (i),11 print("\n")12 for i in reversed(range(10)):13 print (i),14 -------------------------------------------15 Output:1617 30 30 31 31 32 32 33 33 34 34 35 35 36 36 37 37 38 38 39 391819 1 2 3 4 5 6 7 8 9 102021 1 5 92223 9 8 7 6 5 4 3 2 1 0

The range() function generates an arithmetic progression from which i is then taken. Notethat range() is not inclusive. range(60,80) generates the interval starting at 60 up to 79.range(10) generates the interval from 0 to 9.

range() can receive up to three arguments; the first two are the start and end points of thesequence, with the first defaulting to zero. The third argument is the step of the progression.

Finally, if the start index supplied to range() is larger than nothing is produced.1 >>> for i in range(10,1):2 ... print (i)3 ...4 >>>

To force range to produce a descending sequence use the following.

6

Page 7:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 >>> for i in range(10,1,-1):2 ... print(i)3 ...4 105 96 87 78 69 5

10 411 312 2

Notice that 1 is not included.Using for loops with range() has some subtleties to it.

1 #!/usr/bin/env python323 for i in range(10):4 print (i),5 i = i+10006 ---------------------------------------7 Output:89 0

10 111 212 313 414 515 616 717 818 9

We see that the assignment i = i+1000 is carried no effect as i travelled through the loop. Ananswer to this comes by first examining what is returned from range().

1 >>> type(range(10))2 <class ’range’>34 >>> dir(range(10))5 [’__class__’, ’__contains__’, ’__delattr__’, ’__dir__’, ’__doc__’, ’__eq__’,6 ’__format__’, ’__ge__’, ’__getattribute__’, ’__getitem__’, ’__gt__’,7 ’__hash__’, ’__init__’, ’__iter__’, ’__le__’, ’__len__’, ’__lt__’, ’__ne__’,8 ’__new__’, ’__reduce__’, ’__reduce_ex__’, ’__repr__’, ’__reversed__’,9 ’__setattr__’, ’__sizeof__’, ’__str__’, ’__subclasshook__’,

10 ’count’, ’index’, ’start’, ’step’, ’stop’]

We see that range() returns an object of a class named range. We use the dir() function tolearn about the attributes of this object. This object is an iterable object, i.e., one that supportsthe production of an iterator to traverse its internals. We shall revisit this issue in § 11. This isthe reason for the output of the above snippet.

A nice feature of Python loop statements is that they also support an else clause. This is aclause that is executed when the loop terminates through exhaustion of the list (in the case of for)or when the condition becomes false (in the case while), but not when the loop is terminated dueto a break statement. This is exemplified by the following loop, which searches for prime numbersin the range [2, 9].

7

Page 8:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 >>> for n in range(2, 10):2 ... for x in range(2, n):3 ... if n % x == 0:4 ... print(n, ’equals’, x, ’*’, n//x) # // - integer division5 ... break #if this break is executed the else will not be reached6 ... else:7 ... # loop fell through without finding a factor8 ... print(n, ’is a prime number’)9 ...

10 2 is a prime number11 3 is a prime number12 4 equals 2 * 213 5 is a prime number14 6 equals 2 * 315 7 is a prime number16 8 equals 2 * 417 9 equals 3 * 3

While on the subject of mathematical operators here is a short summary of the more commonmathematical operators in Python.

a + b additiona - b subtractiona * b multiplicationa / b divisiona // b integer divisiona % b amod b-a negationabs(a) absolute valuea ** b exponentationmath.sqrt(a)

√a.

To use math.sqrt() on has to import the math module first using import math.The following might be of some interest.

1 >>> -True2 -13 >>> -False4 05 >>> True - 16 07 >>> False - 18 -19 >>> False + 1 -True

10 011 >>> (not False) -112 0

§3.1 Scopes of FOR loops

In C/C++ and Java for loops define their own scope; so that iteration variables that have beendefined in the for header do not exist outside of the scope of the loop. This is not the case inPython.

8

Page 9:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 for i in range(5):2 print(i)3 print(i)4 -------------------------------5 Output:67 08 19 2

10 311 412 4

We see that i still exists outside the for block; and its value is the last value it had with in theloop, in this case it is 4.

Moreover, if i has been defined outside of the loop it will be overrun by the for loop.1 i = 100002 for i in range(5):3 print(i)4 print(i)5 ----------------------------6 Output:78 09 1

10 211 312 413 4

Once this is understood then the following code is not surprising.1 i = 02 while i < 10:3 for i in range(10):4 print(i)5 i+=167 -----------------------------------8 Output:9

10 011 112 213 314 415 516 617 718 819 9

§4 Functions

Python provides functions that perform such common tasks as mathematical calculations, stringmanipulations, character manipulations, Web programming, graphics programming and many otheroperations. To make use of function in a said module one first has to import that module.

9

Page 10:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 #!/usr/bin/env python323 import math4 print (math.sqrt(8800))5 ------------------------------------6 Output:78 93.8083151965

Actually, for this simple program there is no need to import the entire math module; the followingimport would do from math import sqrt. Moreover, if the name sqrt rubs us the wrong waywe may write from math import sqrt as SquareRoot.

We may also define our own functions.1 #!/usr/bin/env python323 def g(x): #define function g4 for i in range(x):5 print (2*i),67 g(10) # apply function89 def f(x): # a function that returns a value

10 return x*x1112 print f(10)

In Python, functions are objects. Indeed, every function is an object of a class called function.1 >>> def f():2 ... pass3 ...4 >>> type(f)5 <class ’function’>

This means that the following is allowed.1 >>> def g():2 ... print("hello")3 ...4 >>> f = g5 >>> f()6 hello

§4.1 Scoping

The use of functions raises the issue of scopes.1 #!/usr/bin/env python323 x = 5 # a global variable45 def f():6 x = 4 #local variable to f7 print (x)8

10

Page 11:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

9 def g():10 global x11 x = x+112 print (x)1314 print x # print global x15 f() # print f’s x16 g() # print global x17 f()18 g()19 -------------------------------------------20 Output:2122 523 424 625 426 7

Python scope resolution is based on what is known as the LEGB rule, which is shorthand forLocal, Enclosing, Global, Built-in; this is the order in which Python looks for variables in theenvironment hierarchy. Though innocent looking some subtleties are still present. Consider thefollowing.

1 >>> x = 102 >>> def f():3 ... x+=14 ... print(x)5 ...6 >>> f()7 Traceback (most recent call last):8 File "<stdin>", line 1, in <module>9 File "<stdin>", line 2, in f

10 UnboundLocalError: local variable ’x’ referenced before assignment

The above error occurs because, when an assignment is performed onto a variable in a scope, thatvariable is automatically considered by Python to be local to that scope and shadows any similarlynamed variable in any outer scope.

§4.2 Passing arguments to functions

Python does not allow programmers to choose between pass-by-value and pass-by-reference whenpassing arguments to functions. Python arguments are always passed by object-reference. Pass-by-object-reference can be thought of as a combination of pass-by-value and pass-by-reference. If afunction receives a reference to a mutable object (e.g., a dictionary or a list, data types we shallsoon meet), then the function can modify the original value of the object. It is as if the object hadbeen passed by reference. If a function receives a reference to an immutable object (e.g., a number,a string or a tuple), then the function cannot modify the original object directly. It is as if the objecthad been passed by value.

11

Page 12:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

§4.3 Returning values from functions

The return statement is used to return values from functions. return without an expressionargument returns None. The following code uses the lambda statement to return an anonymousfunction.

1 >>> def make_incrementor(n):2 ... return lambda x: x + n3 ...4 >>> f = make_incrementor(42)5 >>> f(1)6 437 >>> make_incrementor(2)(10)8 12

Lambda functions can be used wherever function objects are required. They are syntacticallyrestricted to a single expression. Semantically, they are just syntactic sugar for a normal functiondefinition. An alternative way to do this would be to write a nested function as follows.

1 >>> def f(n):2 ... def add_n(x):3 ... return x+n4 ... return add_n5 ...6 >>> f(5)7 <function f.<locals>.add_n at 0x1003c1bf8>8 >>> f(5)(1)9 6

It is often the case that the number of variables to a function is a variable in itself. Pythonprovides a way to cope with such cases.

1 #!/usr/bin/env python323 def f(*args):4 print ("num of args:", len(args))5 sum = 06 for i in args:7 sum += i8 return sum

§4.4 Default values

Python allows us to specify default values for function arguments so that the function can beapplied without this argument.

1 >>> def f(x=5):2 ... return x+13 ...4 >>> f()5 6

When defining a function, Python does not allow an argument without a default argument tofollow and argument that has been supplied with a default value.

12

Page 13:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 >>> def f(x =5, y):2 ... return x+y3 ...4 File "<stdin>", line 15 SyntaxError: non-default argument follows default argument67 >>> def f(x , y= 5): # this however is allowed8 ... return x+y9 ...

10 >>> f(1)11 6

§5 Modules

If you quit from the Python interpreter and enter it again, the definitions you have made (func-tions and variables) are lost. Therefore, if you want to write a somewhat longer program, you arebetter off using a text editor to prepare the input for the interpreter and running it with that fileas input instead. This is known as creating a script. As your program gets longer, you may wantto split it into several files for easier maintenance. You may also want to use a handy function thatyou have written in several programs without copying its definition into each program.

To support this, Python has a way to put definitions in a file and use them in a script or in aninteractive instance of the interpreter. Such a file is called a module; definitions from a module canbe imported into other modules or into the main module (the collection of variables that you haveaccess to in a script executed at the top level and in calculator mode).

A module is a file containing Python definitions and statements. The file name is the modulename with the suffix .py appended. Within a module, the module name (as a string) is availableas the value of the global variable __name__.

1 >>> __name__2 ’__main__’

__main__ is the name of the module the Python interpreter works in.For instance, use your favourite text editor to create a file called test.py in the current directory

with some content. Then enter the Python interpreter and import this module with the followingcommand.

1 >>> import test

This does not enter the names of the functions defined in test directly in the current symbol table;it only enters the module name test there.

1 >>> import math2 >>> sqrt(4) # unrecognised function!!!3 Traceback (most recent call last):4 File "<stdin>", line 1, in <module>5 NameError: name ’sqrt’ is not defined6 >>> math.sqrt(4)7 2.0

13

Page 14:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

If you intend to use, say, a function g from test often it is convenient to assign it to a localname.

1 >>> local_name = test.g

Each module has its own private symbol table, which is used as the global symbol table by allfunctions defined in the module. Thus, the author of a module can use global variables in the modulewithout worrying about accidental clashes with other global variables from other modules. One canrefer directly to global variables and function of a module using the syntax module_name.item_name.

Modules can import other modules. It is customary but not required to place all importstatements at the beginning of a module (or script, for that matter). The imported module namesare placed in the importing module global symbol table.

The files/scripts we have written thus far have been modules which we executed using thepython3 command at the shell.

Python comes with a library of standard modules, the Python Library Reference (Library Ref-erence hereafter). Some modules are built into the interpreter; these provide access to operationsthat are not part of the core of the language but are nevertheless built in, either for efficiency orto provide access to operating system primitives such as system calls. The set of such modules isa configuration option which also depends on the underlying platform. For example, the winregmodule is only provided on Windows systems. One particular module deserves some attention:sys, which is built into every Python interpreter. The variables sys.ps1 and sys.ps2 define thestrings used as primary and secondary prompts

1 >>> import sys2 >>> sys.ps13 ’>>> ’4 >>> sys.ps25 ’... ’6 >>> sys.ps1 = ’C> ’7 C> print(’Yuck!’)8 Yuck!9 C>

The variable sys.path is a list of strings that determines the interpreter search path for mod-ules. It is initialised to a default path taken from the environment variable PYTHONPATH, or froma built-in default if PYTHONPATH is not set.

§5.1 Packages

Packages are a way of structuring Python module namespace by using dotted module names. Forexample, the module name A.B designates a submodule named B in a package named A. Just likethe use of modules saves the authors of different modules from having to worry about each other?sglobal variable names, the use of dotted module names saves the authors of multi-module packages.

14

Page 15:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

§5.2 Namespaces and scopes

A namespace is a mapping from names to objects. Most namespaces are currently implementedas Python dictionaries, but that is normally not noticeable in any way (except for performance),and it may change in the future. Examples of namespaces are: the set of built-in names (containingfunctions such as abs(), and built-in exception names); the global names in a module; and the localnames in a function invocation. In a sense the set of attributes of an object also form a namespace.The important thing to know about namespaces is that there is absolutely no relation between namesin different namespaces; for instance, two different modules may both define a function maximisewithout confusion; users of the modules must prefix it with the module name.

Namespaces are created at different moments and have different lifetimes. The namespace con-taining the built-in names is created when the Python interpreter starts up, and is never deleted.The global namespace for a module is created when the module definition is read in; normally,module namespaces also last until the interpreter quits. The statements executed by the top-levelinvocation of the interpreter, either read from a script file or interactively, are considered part of amodule called __main__, so they have their own global namespace. (The built-in names actuallyalso live in a module; this is called builtins.)

The local namespace for a function is created when the function is called, and deleted when thefunction returns or raises an exception that is not handled within the function. (Actually, forgettingwould be a better way to describe what actually happens.) Of course, recursive invocations eachhave their own local namespace.

A scope is a textual region of a Python program where a namespace is directly accessible. Here,directly accessible means that an unqualified reference to a name attempts to find the name in thenamespace. During execution several scopes may have their namespaces directly accessible:

1. The innermost scope, which is searched first, contains the local names.

2. The scopes of any enclosing functions, which are searched starting with the nearest enclosingscope, contains non-local, but also non-global names.

3. The next-to-last scope contains the current module global names.

4. The outermost scope (searched last) is the namespace containing built-in names.

§6 Basic data types

In § 3 we met the notion of a sequence in Python while discussing the range() function usedin for loop iterations. Python supports three types of sequences: strings, lists, and tuples. Hereare some additional built-in data types we shall be interested in.

15

Page 16:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

complex complex numberint integerfloat floating point numberlist listtuple tupledict dictionaryobject base objectstr string

All of the above types are in fact names of classes. In fact, when we define an integer we actuallydefine an object of class int.

1 >>> l = 82 >>> type(l)3 <class ’int’>

In this section we shall focus on sequence types such as list, tuple, set, and strings.

§6.1 Creating sequences

The creation of each type of sequence has a different syntax.1 #!/usr/bin/env python323 aString = "abc" #creates a string4 emptyString = "" # the empty string5 aList = [1,2,3] # creates a list6 anotherList = list(range(1,11)) # a list of [1,10]7 emptyList = [] # empty list8 aTuple = 1,2,3 # creates a tuple (tuple packing)9 anotherTuple = (1,2,3,4)

10 yetAnotherTuple = tuple(range(1,11))11 emptyTuple = () # the empty tuple12 aSingleton = 1, # a tuple consisting of 1 element - singleton - NOTE THE COMMA!13 aNumber = 1 # not a sequence just an integer

The comma identifies aSingleton to be of a tuple type. Here is another example for creating atuple

1 tuple = (input("1st = "), input("2nd = "), input("3rd = "))23 first, second, third = tuple #(tuple unpacking; lists also support this)45 print (first, second, third)

the same can be done for lists.1 list = [input("1st = "), input("2nd = "), input("3rd = ")]23 first, second, third = list45 print (first, second, third)

16

Page 17:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

A special problem is the construction of tuples containing zero or one items; the syntax has someextra quirks to accommodate these. Empty tuples are constructed by an empty pair of parentheses;a tuple with one item is constructed by following a value with a comma (it is not sufficient to enclosea single value in parentheses).

1 >>> empty = ()2 >>> singleton = ’hello’, # <-- note trailing comma3 >>> len(empty)4 05 >>> len(singleton)6 17 >>> singleton8 (’hello’,)

Next we consider the set data type. A set is an unordered collection with no duplicate elements.Curly braces or the set() function can be used to create sets. Note: to create an empty set you haveto use set(), and not curly braces { }; the latter creates an empty dictionary (see § 7). Hereis a brief demonstration.

1 >>> basket = {’apple’, ’orange’, ’apple’, ’pear’, ’orange’, ’banana’}2 >>> print(basket) # see that duplicates have been removed3 {’orange’, ’banana’, ’pear’, ’apple’}4 >>> ’orange’ in basket # fast membership testing5 True6 >>> ’crabgrass’ in basket7 False8 >>> ’lemon’ not in basket9 True

101112 >>> # Demonstrate set operations on unique letters from two words13 ...14 >>> a = set(’abracadabra’)15 >>> b = set(’alacazam’)16 >>> a # unique letters in a17 {’a’, ’r’, ’b’, ’c’, ’d’}18 >>> a - b # letters in a but not in b19 {’r’, ’d’, ’b’}20 >>> a | b # letters in either a or b21 {’a’, ’c’, ’r’, ’d’, ’b’, ’m’, ’z’, ’l’}22 >>> a & b # letters in both a and b23 {’a’, ’c’}24 >>> a ^ b # letters in a or b but not both25 {’r’, ’d’, ’b’, ’m’, ’z’, ’l’}

§6.2 Sequence indexing

To reference a sequence element we use square brackets. The index of the first element (in anysequence type) is 0.

1 #!/usr/bin/env python323 print (aString[2])4 print (aList[2])5 print (aTuple[2])6 ------------------------------------------------7 Output:

17

Page 18:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

89 c

10 311 3

From the output we learn that the first element of a sequence appears at index zero.1 #!/usr/bin/env python323 aString = "abc"4 print (aString[-3], aString[-2],aString[-1],aString[0],aString[1],aString[2])5 ------------------------------------------------6 Output:78 a b c a b c

Negative indices have a meaning as well. A negative index cannot exceed the length of the sequencein absolute value, and a positive index cannot exceed the length of the sequence minus one.

1 >>> aString = "abc"2 >>> aString[3]3 Traceback (most recent call last):4 File "<pyshell#18>", line 1, in <module>5 aString[3]6 IndexError: string index out of range7 >>> aString[-4]8 Traceback (most recent call last):9 File "<pyshell#19>", line 1, in <module>

10 aString[-4]11 IndexError: string index out of range

Extracting a subsequence (out of any sequence type) can be done using syntax theSeq[start:end].As we saw, start and end can be negative. The subsequence theSeq[start:end] will notcontain theSeq[end]; to include this element, write theSeq[start:end+1]. Here are a fewadditional examples.

1 >>> list = [1,2,3,4,5,6,7,8,9,10]2 >>> list[:]3 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]4 >>> list[0:]5 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]6 >>> list[5:]7 [6, 7, 8, 9, 10]8 >>> list[-10:0]9 []

10 >>> list[:5] # element at index 5 not printed11 [1, 2, 3, 4, 5]12 >>> list[:len(list)] # element at the last index not included13 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

When looping through a sequence, the position index and corresponding value can be retrievedat the same time using the enumerate() function.

1 >>> for i, v in enumerate([’tic’, ’tac’, ’toe’]):2 ... print(i, v)3 ...4 0 tic5 1 tac6 2 toe

18

Page 19:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

To loop over two or more sequences at the same time, the entries can be paired with the zip()function.

1 >>> questions = [’name’, ’quest’, ’favorite color’]2 >>> answers = [’lancelot’, ’the holy grail’, ’blue’]3 >>> for q, a in zip(questions, answers):4 ... print("What is your", q, "?", "It is", a, ".")5 ...6 What is your name? It is lancelot.7 What is your quest? It is the holy grail.8 What is your favorite color? It is blue.

To loop over a sequence in reverse, first specify the sequence in a forward direction and then callthe reversed() function.

1 >>> for i in reversed(range(1, 10, 2)):2 ... print(i)3 ...4 95 76 57 38 1

To loop over a sequence in sorted order, use the sorted() function which returns a new sorted listwhile leaving the source unaltered.

1 >>> basket = [’apple’, ’orange’, ’apple’, ’pear’, ’orange’, ’banana’]2 >>> for f in sorted(set(basket)):3 ... print(f)4 ...5 apple6 banana7 orange8 pear

We now arrive at an important rule. To change a sequence you are iterating over while insidethe loop (for example to duplicate certain items), it is recommended that you first make a copy.Looping over a sequence does not implicitly make a copy. The slice notation makes this especiallyconvenient.

1 >>> words = [’cat’, ’window’, ’defenestrate’]2 >>> for w in words[:]: # Loop over a slice copy of the entire list.3 ... if len(w) > 6:4 ... words.insert(0, w)5 ...6 >>> words7 [’defenestrate’, ’cat’, ’window’, ’defenestrate’]

Here words[:] is a slice of words which happens to be the whole list copy; consequently it is acopy of words. Replacing, words[:] with words will result in a program that never stops.

Let us make this point clear. Consider the following code.1 #!/usr/bin/env python323 elems = [’a’,’b’,’c’]4 for e in elems:5 print (e)

19

Page 20:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

6 elems.remove(e)78 print(elms)9 -----------------------------------

10 Output:11 a12 c13 [’b’]

It is natural to expect that this code would result in iteration over the list printing each elementand then removing it ending up with an empty list. However, this is not the case. The reason forthis is as follows. Upon removing ’a’ the list elem is mutated into [’b’,’c’]. However, ouriterator (created when we used for-in syntax over a container data type) has progressed to ’c’by now; hence the next remove() removes ’c’ and not ’b’. Put another way we can imaginethat Python interprets this code into a lower level code of the following form.

1 elems = [’a’,’b’,’c’]2 i = 03 while i < len(elems):4 e = elems[i]5 print (e)6 elems.remove(e)7 i += 1

Back to our words example. If we replace words[:] with words we have the following. Thefirst call to insert() is done when the iterator reaches w = ’defenestrate’ and alters the listto [’defenestrate’,’cat’, ’window’, ’defenestrate’]. When the iterator advancesto the next word in words it lands on ’defenestrate’ again at the end of the list. This resultin repeating insert(), the mutation and so on resulting in a program that never stops.

§6.3 List comprehensions

List comprehensions provide a concise way to create lists. For instance, assume one seeks a listof squares of numbers in some range. One way of doing this would be as follows.

1 >>> squares = []2 >>> for x in range(10):3 ... squares.append(x**2)4 ...5 >>> squares6 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Alternatively, we could write:1 squares = [x**2 for x in range(10)]

A list comprehension consists of brackets containing an expression followed by a for clause,then zero or more for or if clauses. The result will be a new list resulting from evaluating theexpression in the context of the for and if clauses which follow it. For instance, the followingcreates the cartesian product of two lists minus the pairs containing identical elements.

1 >>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]2 [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

20

Page 21:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

This is equivalent to1 >>> combs = []2 >>> for x in [1,2,3]:3 ... for y in [3,1,4]:4 ... if x != y:5 ... combs.append((x, y))6 ...7 >>> combs8 [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

List comprehensions can contain complex expressions and nested functions.1 >>> from math import pi2 >>> [str(round(pi, i)) for i in range(1, 6)]3 [’3.1’, ’3.14’, ’3.142’, ’3.1416’, ’3.14159’]

The initial expression in a list comprehension can be any arbitrary expression, including anotherlist comprehension. Consider the following example of a 3× 4 matrix implemented as a list of 3 listsof length 4.

1 >>> matrix = [2 ... [1, 2, 3, 4],3 ... [5, 6, 7, 8],4 ... [9, 10, 11, 12],5 ... ]

The following list comprehension will transpose rows and columns.1 >>> [[row[i] for row in matrix] for i in range(4)]2 [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

This is equivalent to:1 >>> transposed = []2 >>> for i in range(4):3 ... transposed.append([row[i] for row in matrix])4 ...5 >>> transposed6 [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Or the same as this:1 >>> transposed = []2 >>> for i in range(4):3 ... # the following 3 lines implement the nested listcomp4 ... transposed_row = []5 ... for row in matrix:6 ... transposed_row.append(row[i])7 ... transposed.append(transposed_row)8 ...9 >>> transposed

10 [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Consider now the list comprehension1 >>> [lambda x: i*x for i in range(5)]2 [<function <listcomp>.<lambda> at 0x10213d268>,3 <function <listcomp>.<lambda> at 0x10213d2f0>,4 <function <listcomp>.<lambda> at 0x10213d378>,5 <function <listcomp>.<lambda> at 0x10213d400>,6 <function <listcomp>.<lambda> at 0x10213d488>]

21

Page 22:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

which generates a list of functions. The intuitive intention behind this is to create a list, say lst,of functions so that lst[i] is a function that receives an number x and returns i*x. This is notwhat this list contain.

1 >>> l[0](1)2 43 >>> l[1](1)4 45 >>> l[2](1)6 47 >>> l[3](1)8 49 >>> l[4](1)

10 4

All the functions multiply their argument by 4 and not by the relative index. This happens dueto Python binding behaviour for variables and values. In Python we have seen that the type of avariable is resolved during runtime, usually through an assignment statement; we can think of this asresolving the type of the variable with respect to the relevant context in which it is defined. Pythontakes a similar road when functions and methods are called; indeed, method and function invocationare resolved by name and context (i.e., the parameters passed) during runtime. This behaviour orapproach is referred to as late or dynamic binding.

Now in the current example, at the time that the functions are defined the variable i is notresolved or is bounded to the relative value it has in the loop (of the list comprehension) this isbecause the function is being defined and i is not used (so there is no need to resolve its name intoa value).

When the loop is exhausted the variable i falls out of memory. However, each of the functiondefinitions in the list carries a variable i and so in the table of variable names of each function thename i appears. The value Python associates with this variable in each table is 4; the last value itassumes in the loop.

When any of the functions in the list are called/invoked, the value of the variable i is looked upin the surrounding scope at the time of the function invocation. This value we know now is 4.

We may use default values to solve this problem.1 >>> l = [lambda x, i=i: i * x for i in range(5)]2 >>> l[0](1)3 04 >>> l[1](1)5 16 >>> l[2](1)7 28 >>> l[3](1)9 3

10 >>> l[4](1)11 412 >>>

We have used a default argument to "plug-in" the relevant value of i so that at the time of invocationof the function the correct value will be used. This works since to define a default value an assignmenthas to take place which forces Python to resolve i right then and there.

Similarly to list comprehensions, set comprehensions are also supported.

22

Page 23:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 >>> a = {x for x in ’abracadabra’ if x not in ’abc’}2 >>> a3 {’r’, ’d’}

§6.4 Non homogenous data types

Unlike strings that are homogeneous in the sense that every sequence element is a character, listsand tuples need not be homogeneous; the following is perfectly legal in Python.

1 >>> l = ["abc",[1,2,["def",3,4], ("ghi",4,8.0,[16,17])]] # a list2 >>> t = (1,"abc",3.7,[1,2,["r","t"]]) # a tuple

Here we see that lists and tuples are in fact recursive in the sense that they may contain lists andtuples as elements. Indexing nested sequences is done as follows.

1 >>> l = ["abc",[1,2,["def",3,4], ("ghi",4,8.0,[16,17])]]2 >>> l[1]3 [1, 2, [’def’, 3, 4], (’ghi’, 4, 8.0, [16, 17])]4 >>> l[1][2]5 [’def’, 3, 4]6 >>> l[1][2][0]7 ’def’8 >>> l[1][2][0][1]9 ’e’

The difference between lists and tuples (other than the syntax) is mainly conceptual; think ofa tuple like a single record or a row in database table. We should recall though that we havealready met one important non-conceptual difference between tuples and lists; the latter are passedby reference to functions while tuples by value; indeed, lists are mutable and tuples are immutable.

1 >>> list = [1,2,3]2 >>> list[2] = 1003 >>> list4 [1, 2, 100]5 >>> tuple = (1,2,3)6 >>> tuple[2] = 1007 Traceback (most recent call last):8 File "<pyshell#5>", line 1, in <module>9 tuple[2] = 100

10 TypeError: ’tuple’ object does not support item assignment

It seems as though lists have made tuples redundant. Here are a few reasons why one would liketo have the tuple data type.

1. Tuples, being immutable, are faster than lists. Use these for definitions of constants.

2. May make code safer.

3. Dictionaries, a data type we shall meet soon, must have their keys immutable. That is keyscan thus be, for instance, numbers, strings, and also tuples, but not lists.

23

Page 24:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

§6.5 List manipulation

In Python it is exceedingly simple to write a function that returns a list of the numbers of theFibonacci series, instead of printing it.

1 >>> def fib2(n): # return Fibonacci series up to n2 ... #Return a list containing the Fibonacci series up to n3 ... result = []4 ... a, b = 0, 15 ... while a < n:6 ... result.append(a) # see below7 ... a, b = b, a+b8 ... return result9 ...

10 >>> f100 = fib2(100) # call it11 >>> f100 # write the result12 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

append() adds an element to the end of a list. The following code demonstrate addition ofelements into a list without append.

1 #!/usr/bin/env python323 list = [] # an empty list45 for i in range (10): # populate the list6 list += [int(input(">> Enter element: "))] # add to list78 sum = 09

10 for i in list: # calculate the sum11 sum+= i1213 print ("sum is: ", sum)

Regarding the use of += care must be taken:1 >>> lst = [1,2,3]2 >>> lst += 53 Traceback (most recent call last):4 File "<stdin>", line 1, in <module>5 TypeError: ’int’ object is not iterable6 >>> lst+= [5]7 >>> lst8 [1, 2, 3, 5]

Lists, strings and dictionaries (the latter we shall meet in § 7) "contain" methods/functions.We may easily use lists as stacks.

1 >>> stack = [3, 4, 5]2 >>> stack.append(6)3 >>> stack.append(7)4 >>> stack5 [3, 4, 5, 6, 7]6 >>> stack.pop() #pop() removes last element7 78 >>> stack9 [3, 4, 5, 6]

10 >>> stack.pop()11 612 >>> stack.pop()13 5

24

Page 25:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

14 >>> stack15 [3, 4]

It is also possible to use a list as a queue. However, lists are not efficient for this purpose. Whileperforming append() and pop() to the end of list are fast, performing pop() from the head of alist is slow (because all of the other elements have to be shifted by one).

1 #!/usr/bin/env python323 queue = [1,2,3]45 queue.pop(0) # deque using pop()67 print(queue)89 queue.insert(len(queue),4) # enque using insert()

1011 print(queue)

Thus, to implement a queue then one would either implement their own queue class or usecollections.deque which was designed to have fast appends and pops from both ends.

1 >>> from collections import deque2 >>> queue = deque(["Eric", "John", "Michael"])3 >>> queue.append("Terry") # Terry arrives4 >>> queue.append("Graham") # Graham arrives5 >>> queue.popleft() # The first to arrive now leaves6 ’Eric’7 >>> queue.popleft() # The second to arrive now leaves8 ’John’9 >>> queue # Remaining queue in order of arrival

10 deque([’Michael’, ’Terry’, ’Graham’])

In the following we introduce some additional methods supported by the list data type. To thisend let us set up the list

1 #!/usr/bin/env python323 list = [10,9,10,2,8,6,9,7,6,4,5,4,8,3,7,2,1,3]

To count the occurrences of an element use count().1 # counting occurrences2 print ("\nCounting \n")3 print ("10 appears %d times " % list.count(10))4 print ("8 appears %d times " % list.count(8))5 print ("1 appears %d times " % list.count(1))6 ------------------------------------------------7 Output:89 Counting

1011 10 appears 2 times12 8 appears 2 times13 1 appears 1 times

To extend a list by a whole list at the end use extend(). Note that append() adds a singleelement at the end.

25

Page 26:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 someList = [4.6, "abc", 7.10, "hello"]2 list.extend(someList)34 print ("\nExtending \n")5 print ("The new list is: \n", list)67 ------------------------------------------------8 Output:9

10 Extending1112 The new list is:13 [10, 9, 10, 2, 8, 6, 9, 7, 6, 4, 5, 4, 8, 3, 7, 2, 1, 3, 4.6, ’abc’, 7.1, ’hello’]

To find the first occurrence of an element use index().1 # finding the index of the first occurrence of an element2 print ("\nLocating index\n")3 print ("\nThe first index of ’abc’ is %d" % list.index("abc")) # locate the ’abc’ string4 print ("The first occurrence of 3 is %d" % list.index(3))56 ------------------------------------------------7 Output:89 Locating index

101112 The first index of ’abc’ is 1913 The first occurrence of 3 is 13

To remove the first occurrence of an element use remove().1 print ("\nRemoving elements\n")2 print ("The list prior to removal is: \n", list)3 list.remove(10)4 list.remove(9)5 print ("\nThe list after removal is :\n", list )6 ------------------------------------------------7 Output:89 Removing elements

1011 The list prior to removal is:12 [10, 9, 10, 2, 8, 6, 9, 7, 6, 4, 5, 4, 8, 3, 7, 2, 1, 3, 4.6, ’abc’, 7.1, ’hello’]1314 The list after removal is :15 [10, 2, 8, 6, 9, 7, 6, 4, 5, 4, 8, 3, 7, 2, 1, 3, 4.6, ’abc’, 7.1, ’hello’]

To insert an element at a specified index use insert().1 print "\nInserting an element at a specified index\n"2 list.insert(0,500)3 list.insert(1, 345)4 print ("After insertion the list looks like: \n", list)5 ------------------------------------------------6 Output:78 Inserting an element at a specified index9

10 After insertion the list looks like:11 [500, 345, 10, 2, 8, 6, 9, 7, 6, 4, 5, 4, 8, 3, 7, 2, 1, 3, 4.6, ’abc’, 7.1, ’hello’]

26

Page 27:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

To pop an element at a given index use pop(). Recall that pop() without an argument removesan element at the end of the list.

1 print ("\nPopping\n")2 print ("The last element is:\n ", list.pop()) # if no index is given last element is removed3 print ("\nHere is the list now: \n", list)4 print ("\nThe fifth element is: " , list.pop(5))5 print ("\nThe list now is: \n", list)6 ------------------------------------------------7 Output:89 Popping

1011 The last element is:12 hello1314 Here is the list now:15 [500, 345, 10, 2, 8, 6, 9, 7, 6, 4, 5, 4, 8, 3, 7, 2, 1, 3, 4.6, ’abc’, 7.1]1617 The fifth element is: 61819 The list now is:20 [500, 345, 10, 2, 8, 9, 7, 6, 4, 5, 4, 8, 3, 7, 2, 1, 3, 4.6, ’abc’, 7.1]

To reverse a list use reverse()1 print ("\nReversing a list\n")2 list.reverse()3 print ("The list reversed: \n", list)4 ------------------------------------------------5 Output:67 Reversing a list89 The list reversed:

10 [7.1, ’abc’, 4.6, 3, 1, 2, 7, 3, 8, 4, 5, 4, 6, 7, 9, 8, 2, 10, 345, 500]

§6.5.1 A word of caution

Many of the methods we see here return nothing.1 >>> l = [1,2,3,4].reverse()2 >>> l3 >>> # nothing is printed!!!!!!

§6.6 Comparing lists

Lists being objects can be compared in more than one way. Let us first consider the issue ofcomparing content.

1 >>> l1 = [1,2,3,4]2 >>> l2 = [4,2,3,1]3 >>> l1 < l24 True

27

Page 28:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Indeed, l1 is lexicographically smaller than l2. Python supports operator overloading ; this meansthat classes who implement, say, the special built-in method __lt__() (stands for "less than") willhave the operator < supported for their objects. Lists in python are objects of the class list thatdoes that amongst other things implements __lt__(). We shall encounter this issue again oncewe consider object oriented programming in Python.

Suppose we wish to know whether two lists contain the same content which may appear indifferent order as we have above. Here is one way to go about it.

1 >>> [i for i in l1 for j in l2 if i == j]2 [1, 2, 3, 4]

We can then compare the len() of the resulting list with the len() of l1 or l2.Next, we introduce the operators is and is not and compare these with ==.

op1 == op2 True provided op1 and op2 have the same valueop1 is op2 True if op1 and op2 point to the same object.op1 is not op2 Inverse of is

1 >>> l1 = [1,2,3,4]2 >>> l2 = [1,2,3,4]3 >>> l1 == l24 True5 >>> l1 is l26 False7 >>> l3 = l18 >>> l1 is l39 True

What can we infer from this code?

§6.7 Sorting lists

To sort a list in ascending order use sort().1 print ("\nSorting a list\n")2 list.append("bbc") # lets add one more string for interest3 list.sort()4 print ("The list sorted: \n", list)5 ------------------------------------------------6 Output:78 Sorting a list9

10 The list sorted:11 [1, 2, 2, 3, 3, 4, 4, 4.6, 5, 6, 7, 7, 7.1, 8, 8, 9, 10, 345, 500, ’abc’, ’bbc’]

Note that list.sort() sorted both the numerical values and the strings in the list. Let us lingera bit more on list.sort(). This next example shows that tuples are ordered lexicographically.

1 >>> list = [(1,2,3),(1,1,3)]2 >>> list.sort()3 >>> list4 [(1, 1, 3), (1, 2, 3)]

28

Page 29:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

56 >>> list = [(1,1,3),(1,1,3,0)]7 >>> list.sort()8 >>> list9 [(1, 1, 3), (1, 1, 3, 0)]

Previously we saw that list.sort() can handle lists with numerical values and strings. What ifwe throw tuples into the mix?

1 >>> list = ["aa",(1,2,3),3,"a",(1,1,3),3.0001, "aaa", (1,1,3)]2 >>> list.sort()3 Traceback (most recent call last):4 File "<pyshell#7>", line 1, in <module>5 list.sort()6 TypeError: unorderable types: tuple() < str()

Python complains that it cannot order strings and tuples; does that mean that numerical values andtuples can be sorted?

1 >>> list = [1,(1,1,3),(1,1,3,1),0.9]2 >>> list.sort()3 Traceback (most recent call last):4 File "<pyshell#9>", line 1, in <module>5 list.sort()6 TypeError: unorderable types: tuple() < int()

Sequence objects may be compared to other objects with the same sequence type. The compar-ison uses lexicographical ordering. Note that comparing objects of different types with < or > islegal provided that the objects have appropriate comparison methods. For example, mixed numerictypes are compared according to their numeric value, so 0 equals 0.0, etc. Otherwise, rather thanproviding an arbitrary ordering, the interpreter will raise a TypeError exception.

list.sort() is quite versatile as it allows us to pass it a function according to which thecomparisons will be made.

1 #!/usr/bin/env python32 def comparator(x,y):3 if x [1] > y[1] :4 return 15 elif x[1] < y[1]:6 return -17 else:8 return 09

10 list = ["afa", "dca", "zaz" ]11 list.sort(comparator)12 print (list)13 -------------------------------------------14 Output:1516 [’zaz’, ’dca’, ’afa’]

The agreement regarding the comparison function is as follows. The function has to receive twoarguments, say, x and y in this order. To have x < y the function must return −1, it must return1 to have x >y and 0 otherwise. For instance, in our case comparator("afa","dca") returns1.

29

Page 30:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

To sort a list in descending order instead of ascending we may use theList.sort(reverse= True).

§6.7.1 sort() and sorted()

The list class, as we have just seen, supports a sort() method. This method changes the objectit was invoked upon and returns nothing.

1 >>> l = [4,3,2,1].sort()2 >>> l3 >>>

sorted() is a built-in function and not a method of list which applies to more object than lists.1 >>> sorted([4,3,2,1]) # applied to lists2 [1, 2, 3, 4]3 >>> sorted((4,3,2,1)) # applied to tuples4 [1, 2, 3, 4]5 >>> sorted({4,3,2,1}) # applied to sets6 [1, 2, 3, 4]

Note that tuples, being immutable, do not support a method such as sort(). Use sorted(iterableElem,reverse = True) to sort in descending order.

§6.7.2 reverse() and reversed()

We considered the issue of the list method sort() vs. the built-in function sorted(). Whatabout the method reverse() of list and the built-in function reversed()? We have met thelatter when we considered reversed ranges in for loops by applying reversed(range(10)).

1 >>> reversed([1,2,3,4])2 <list_reverseiterator object at 0x10203a860>

However, when applying reversed() to a list, then, not similar to say sorted(), we do notget a list back and in particular not a reversed list. We get an iterator object. Indeed, we usedreversed(range(10)) within the confines of the iterator generation syntax of the for loop.

§6.8 The DEL statement

Another way to remove elements from a list is using the del command. This differs from thepop() method which returns a value. The del statement can also be used to remove slices from alist or clear the entire list (which we did earlier by assignment of an empty list to the slice).

1 >>> a = [-1, 1, 66.25, 333, 333, 1234.5]2 >>> del a[0]3 >>> a4 [1, 66.25, 333, 333, 1234.5]5 >>> del a[2:4]6 >>> a7 [1, 66.25, 1234.5]

30

Page 31:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

8 >>> del a[:]9 >>> a

10 []

del can also be used to delete entire variables:1 >>> del a

§6.9 A few additional examples for lists

Let us consider the following examples.1 >>> vec = [-4, -2, 0, 2, 4]2 >>> # create a new list with the values doubled3 >>> [x*2 for x in vec]4 [-8, -4, 0, 4, 8]5 >>> # filter the list to exclude negative numbers6 >>> [x for x in vec if x >= 0]7 [0, 2, 4]8 >>> # apply a function to all the elements9 >>> [abs(x) for x in vec]

10 [4, 2, 0, 2, 4]11 >>> # call a method on each element12 >>> freshfruit = [’ banana’, ’ loganberry ’, ’passion fruit ’]13 >>> [x.strip() for x in freshfruit]14 [’banana’, ’loganberry’, ’passion fruit’]15 >>> # create a list of 2-tuples like (number, square)16 >>> [(x, x**2) for x in range(6)]17 [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]18 >>> # the tuple must be parenthesised, otherwise an error is raised19 >>> [x, x**2 for x in range(6)]20 File "<stdin>", line 1, in ?21 [x, x**2 for x in range(6)]22 ^23 SyntaxError: invalid syntax2425 >>> # flatten a list with two ’for’26 >>> vec = [[1,2,3], [4,5,6], [7,8,9]] # rule: upper to inner level27 >>> [num for elem in vec for num in elem]28 [1, 2, 3, 4, 5, 6, 7, 8, 9]

§6.10 Methods for SET

Here is a brief summary of some of the methods supported by the set class. In the table belowother may stand for objects of class list and tuple and not only set.

31

Page 32:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

isdisjoint()issubset()set <= other ≡ set.issubset(other)set < other is strict subsetissuperset()set >= other ≡ set.issuperset(other)set > other is strict supersetunion()set | other ≡ set.union(other)intersection()set & other ≡ set.intersection(other)difference()set - other ≡ set.difference(other)symmetric_difference(other)set ˆ other ≡ set.symmetric_difference(other)add()remove()discard()pop()clear()

§6.11 Functions revisited

Python allows for arguments of a function to be optional as long as a default value has beenprovided.

1 >>> def f(x = 5):2 ... return x+13 ...4 >>> f()5 6

When the default value is a mutable type problems may occur.1 >>> def foo(bar = []):2 ... bar.append("baz")3 ... return bar4 ...5 >>>

A common mistake is to think that the optional argument will be set to the specified defaultexpression each time the function is called without supplying a value for the optional argument.Indeed, this is the case with the function f() above where x is set to an immutable integer object.Consider the following.

1 >>> foo()2 [’baz’]3 >>> foo()

32

Page 33:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

4 [’baz’, ’baz’]5 >>> foo()6 [’baz’, ’baz’, ’baz’]

The default value for a function argument is only evaluated once, at the time that the functionis defined. Thus, the bar argument is initialised to its default (i.e., an empty list) only when foo()is first defined, but then calls to foo() (i.e., without a bar argument specified) will continue to usethe same list to which bar was originally initialised.

Here is one way to circumvent this issue.1 >>> def foo(bar=None):2 ... if bar is None:3 ... bar = []4 ... bar.append("baz")5 ... return bar6 ...7 >>> foo()8 ["baz"]9 >>> foo()

10 ["baz"]11 >>> foo()12 ["baz"]

Let us recall the rule Python abides by regarding assignments to variables: a variable beingassigned a value is considered local to the current scope. When this variable is a list object theoverloaded operators supported by class list introduce the following problem.

1 >>> lst = [1,2,3]2 >>> def f():3 ... lst+= [5]4 ...5 >>> f()6 Traceback (most recent call last):7 File "<stdin>", line 1, in <module>8 File "<stdin>", line 2, in f9 UnboundLocalError: local variable ’lst’ referenced before assignment

To avoid this issue, we can use append()1 >>> def g():2 ... lst.append(5)3 ...4 >>> g()5 >>> lst6 [1, 2, 3, 5]

§7 Dictionaries

In the previous section we considered sequence types. Here we consider dictionaries (also calledhash maps or associative arrays in other languages) are mapping constructs consisting of key-valuepairs; and hence called mapping types. We have seen that curly braces create sets; they also createdictionaries.

1 #!/usr/bin/env python3

33

Page 34:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

23 d = {} # the empty dictionary

Use the syntax key : value to create associations.1 d = {"alpha" : "A", "beta" : "B"}

Another way to create dictionaries is using the built-in function dict().1 >>> a = dict(one=1, two=2, three=3)2 >>> b = {’one’: 1, ’two’: 2, ’three’: 3}3 >>> c = dict(zip([’one’, ’two’, ’three’], [1, 2, 3]))4 >>> d = dict([(’two’, 2), (’one’, 1), (’three’, 3)])5 >>> e = dict({’three’: 3, ’one’: 1, ’two’: 2})6 >>> a == b == c == d == e7 True

To accesses values by keys we may use the square brackets.1 print (d["alpha"]) # access the value corresponding to key "alpha"2 -------------------------------------------3 Output:45 A

Here we reset the value of an already existing key.1 d["alpha"] = "AA" # reset the value of "alpha"2 print(d)3 -------------------------------------------4 Output:56 {’alpha’:’AA’, ’beta’:’B’}

Here we add a new association.1 d["kappa"] = "K" # add a new association2 print (d)3 -------------------------------------------4 Output:56 {’alpha’: ’AA’, ’beta’: ’B’, ’kappa’: ’K’}

Here is another example where we alter the value associated with a key.1 >>> b = {1:[],2:["a"]}2 >>> b[1].append("b")3 >>> b4 {1: [’b’], 2: [’a’]}

To remove associations we may use the del statement.1 del d["beta"] # remove an association2 print (d)34 -------------------------------------------5 Output:67 {’alpha’: ’AA’, ’kappa’: ’K’}

34

Page 35:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

The agreement here is that pairs are arranged in the form (key : value).

Like lists, dictionaries also supports their own methods. We exemplify a few here. To that endlet us set up a dictionary object.

1 #!/usr/bin/env python323 d = {1:"abc",2:"def",3:"ghi",4: "jkl" , 5: "mno", 6: "pqr" , 7:"stu" , 8: "vwx", 9:"yz"}

Use get() to retrieve value for a given key.1 print ("\nRetrieve value per key\n")23 print ("(1 to ", d.get(1), ")", "(2 to ", d.get(2), ")", "(3 to ", d.get(3), ")")4 print (d.get(10)) # we don’t have key 1056 -------------------------------------------7 Output:8 Retrieve value per key9

10 (1 to abc ) (2 to def ) (3 to ghi )11 None

The method of dict in Python 2.x called has_key() used to check the existence of a key ina dictionary has been deprecated and replaced by the operator in as follows.

1 key = int(input("\nEnter key to search: "))2 if key in d:3 print ("We have this key; its value is: ", d.get(key))4 else:5 print ("No such key")67 -------------------------------------------8 Output:9

10 Enter key to search: 1011 No such key

We may add entire dictionaries using update().1 newD = {1.1: "abc1", "2.1.1":"def1"}2 d.update(newD)3 print ("\nAfter updating the dictionary is: \n", d)45 -------------------------------------------6 Output:78 After updating the dictionary is:9 {1: ’abc’, 2: ’def’, 3: ’ghi’, 4: ’jkl’, 5: ’mno’, 6: ’pqr’,

10 7: ’stu’, 8: ’vwx’, 9: ’yz’, 1.1: ’abc1’, ’2.1.1’: ’def1’}

If the new dictionary (i.e., the argument to update() has keys that collide with keys in the olddictionary then the values of the keys in the old dictionary are overridden.

1 >>> d = {1:"A",2:"B"}2 >>> d1 = {1:"A",2:"B"}3 >>> d2 = {1:"C",3:"D"}4 >>> d1.update(d2)5 >>> d16 {1: ’C’, 2: ’B’, 3: ’D’}

35

Page 36:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

The popitem() method returns an arbitrary element from the dictionary and removes it. Thisis useful if one seeks to iterate over a dictionary in no specific order, process the entries and at thesame time empty the dictionary.

§7.1 Views into dictionaries

Three methods of class dict provide views into the internals of dictionaries, these are keys(),values(), and items().

In Python 2.x the keys() method would return a list containing the keys and so we could havethe following code.

1 print ("\nThe keys are now: \n", d.keys())23 -------------------------------------------4 Output:56 The keys are now:7 [1, 2, 3, 4, 5, 6, 7, 8, 9, 1.1, ’2.1.1’]

In Python 3.x keys() returns an object of class dict_keys.1 >>> d = {1:"a",2:"b",3:"c"}2 >>> d.keys()3 dict_keys([1, 2, 3])4 >>> type(d.keys())5 <class ’dict_keys’>

Examining the attributes returned from keys() we that there are no methods to manipulate thekeys represented by this object of dict_keys.

1 >>> d_keys = d.keys()2 >>> dir(d_keys)3 [’__and__’, ’__class__’, ’__contains__’, ’__delattr__’, ’__dir__’, ’__doc__’, ’__eq__’,4 ’__format__’, ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’, ’__init__’, ’__iter__’,5 ’__le__’, ’__len__’, ’__lt__’, ’__ne__’, ’__new__’, ’__or__’, ’__rand__’, ’__reduce__’,6 ’__reduce_ex__’, ’__repr__’, ’__ror__’, ’__rsub__’, ’__rxor__’, ’__setattr__’, ’__sizeof__’,7 ’__str__’, ’__sub__’, ’__subclasshook__’, ’__xor__’, ’isdisjoint’]

In fact we see, from the list of methods above that an object of class dict_keys acts more as a set;indeed, the keys of any dictionary must form a set. The idea here is that the dict_keys objectacts as a mere view into the dictionary; in particular, altering it should not alter the dictionary andthus providing a higher level of data encapsulation for the data structure.

How do we interact with the keys of dictionaries then? One way is to generate lists out of thekeys, for instance as follows.

1 >>> list(d) # get a list of the keys2 [1, 2, 3]3 >>> k = sorted(d) # sort keys4 >>> k5 [1, 2, 3]6 >>> sorted(d.keys()) # sort keys7 [1, 2, 3]

Another way is to work with the dict_keys object as follows.

36

Page 37:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 >>> 1 in d.keys()2 True3 >>> for key in d.keys():4 ... print(key)5 ...6 17 28 3

Dictionary view objects are dynamic in the sense that they do reflect changes made to the dictionary.1 >>> d = {1:"a",2:"b",3:"c"}2 >>> keys = d.keys() # get a view3 >>> del d[1]4 >>> keys5 dict_keys([2, 3])6 >>> d[1] = "hello"7 >>> keys8 dict_keys([1, 2, 3])

The values() method is similar to keys() only that it provides a view of the values insteadof the keys. The class providing the view is called dict_values.

1 >>> d.values()2 dict_values([’a’, ’b’, ’c’])3 >>> list(d.values()) # generate a list of the values4 [’a’, ’b’, ’c’]5 >>> dir(d.values())6 [’__class__’, ’__delattr__’, ’__dir__’, ’__doc__’, ’__eq__’, ’__format__’,7 ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’, ’__init__’, ’__iter__’,8 ’__le__’, ’__len__’, ’__lt__’, ’__ne__’, ’__new__’, ’__reduce__’, ’__reduce_ex__’,9 ’__repr__’, ’__setattr__’, ’__sizeof__’, ’__str__’, ’__subclasshook__’]

When looping over dictionaries, it is often useful to have the keys and values iterated over inpairs. We can accomplish this using the method items().

1 >>> knights = {’gallahad’: ’the pure’, ’robin’: ’the brave’}2 >>> for k, v in knights.items():3 ... print(k, v)4 ...5 gallahad the pure6 robin the brave

Like values() and keys(), the items()method also return a view; an object of class dict_items.

§7.2 The MAP() built-in function

Dictionaries are a a type of map. A useful built-in function that allows generation of complicatediterable objects is the map() function.

1 >>> for i in map(lambda x,y: x*y, [1,2,3,4],[5,6,7,8]):2 ... print(i)3 ...4 55 126 217 32

37

Page 38:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Note that as the function passed to map() has two arguments we had to supply to map() two lists;more variables means more lists that must be passed.

§8 Built-in functions for "conversions"

Throughout we have met the expression "built-in" functions on more than one occasion. Forinstance, we have met int(), sorted(), reversed(), abs(), zip(), len(), range(),print(), input(), and sum().

Here, we seek to focus on built-in function that "convert" between types. For instance likeint() which receives an integer or a string and returns an integer object.

1 >>> l = int(8)2 >>> l3 84 >>> type(l)5 <class ’int’>

Here we used the built-in function type() to discover that l is an object of classint (an immutableobject of course). The str() function creates objects of class str.

1 >>> l = str(7.8)2 >>> l3 ’7.8’4 >>> type(l)5 <class ’str’>

Similarly to int() we have float() and complex() that generate objects of their respectiveclasses. Our interest here is with the functions list(), tuple(), set(), and dict(). As theirnames imply these generate objects of their respective classes.

Suppose one seeks to compare two lists and find the matches of these lists. One way to do that isto construct set object of these lists and work with those. The fact that we lose duplicates entriesof the lists is irrelevant as to keep track of matches (per value) we need keep only one copy. Hereare a few ways to do that.

1 >>> l1 = [1,3,5,7,8,12]2 >>> l2 = [3,4,5,6,7,12]3 >>> set(l1) & set(l2)4 {3, 12, 5, 7}5 >>> set(l1).intersection(l2)6 {3, 12, 5, 7}

We may "convert" the results back to lists using list().

§9 Object oriented programming

In Python there are no public, private, and protected keywords; all class members (meth-ods and data) are always "public" in the sense of C/C++ and Java. In fact there is no way to declare

38

Page 39:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

private variables (see § 9.1 for a more detailed explanation). Moreover, all class methods are virtual(in C++ terminology)1.

In the Python literature it is common to refer to class members as attributes. Also, from here onend, we shall distinguish between functions and methods where the latter are functions associatedwith classes, where the former are not.

Unlike, say, C++ and Java there are no shorthands for referencing the members of an objectfrom its own methods; every method is declared with an explicit first argument representing theobject, which is provided implicitly by the call; this first argument is called self.

When a class definition is entered, a new namespace is created, and used as the local scope; thus,all assignments to local variables go into this new namespace. In particular, function definitionsbind the name of the new function here. When a class definition is left normally (via the end), aclass object is created. This is basically a wrapper around the contents of the namespace created bythe class definition

Let us now write two files; Process1.py which will contain a class called Process and a filefile.py which will create an instance of this class and invoke its methods. Here is the Processclass.

1 #!/usr/bin/env python32 #Process1.py34 class Process:5 """Class Process documentation string:6 Document the class here7 and here......8 and here """9

10 #the constructor11 def __init__(self,name = "",start ="",end="",location = "",priority=0 ):12 """ document the constructor """13 self.name = name #instance attributes14 self.start = start15 self.end = end16 self.location = location17 self.priority = priority1819 def representation(self):20 print (self.name, self.start, self.end,self.location, self.priority)2122 def terminate(self,end):23 self.end = end24 print (self.name, "has terminated at", self.end)

In this file we see an example of multiline comments.Functions were introduced in § 4; in the definition of class Process we encounter similar syntax

to that of defining functions; those are the methods of the class. That is we refer to class functionsas methods and to global functions as functions.

Each method2 of Process receives at least one parameter; this includes the constructor __init__().This parameter is a reference to the specific instance on which the class method is invoked; here it is

1In object-oriented programming, a virtual function or virtual method is a function or method whose behaviourcan be overridden within an inheriting class by a function with the same signature.

2Non-static method that is.

39

Page 40:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

referred to as self. When the method is invoked, this parameter is passed implicitly, we are onlyto specify the reminder of the arguments.

When a class defines an __init__()method, class instantiation automatically invokes __init__()for any newly-created class instances. __init__() receives self and "adds" to this instance theclass variables together with their initial values. This approach enables the instances of the sameclass to have different attributes.

1 >>> class X:2 ... def __init__(self,x):3 ... if x == 0:4 ... self.z = 05 ... else:6 ... self.w = 17 ...8 >>> x1 =X(0)9 >>> x2 =X(1)

10 >>> x1.z11 012 >>> x2.z13 Traceback (most recent call last):14 File "<stdin>", line 1, in <module>15 AttributeError: ’X’ object has no attribute ’z’16 >>> x2.w17 118 >>> x1.w19 Traceback (most recent call last):20 File "<stdin>", line 1, in <module>21 AttributeError: ’X’ object has no attribute ’w’

This is radically different from say Java and C++; where the point of view regarding a class definitionis that such behaves as a blueprint.

By providing default values to the parameters of the constructor we enable a default construc-tion, and other forms of constructions. We could have written the class of the example without__init__() but then we would have to initialise the ember variables separately instead of usingthe built-in mechanism provided by Python.

1 >>> class X:2 ... pass3 ...4 >>> x = X()5 >>> type(x)6 <class ’__main__.X’>7 >>> x.data = 58 >>> def f(x):9 ... print(x)

10 ...11 >>> x.g = f12 >>> x.g("hello")13 hello

We have manually added a variable data and a function g to the instance object x of class X. Wecan verify that we have added those by using the dir() function that return a (partial) list of theattributes set for a given object.

1 >>> dir(x)2 [’__class__’, ’__delattr__’, ’__dict__’, ’__dir__’, ’__doc__’, ’__eq__’,3 ’__format__’, ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’,

40

Page 41:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

4 ’__init__’, ’__le__’, ’__lt__’, ’__module__’, ’__ne__’, ’__new__’,5 ’__reduce__’, ’__reduce_ex__’, ’__repr__’, ’__setattr__’, ’__sizeof__’,6 ’__str__’, ’__subclasshook__’, ’__weakref__’, ’data’, ’g’]7 >>>

We see data and g at the end. Also note that __init__() is also on the list despite us notdefining it explicitly. Notice however, that both data and g are not listed as attributes of X.

1 >>> dir(X)2 [’__class__’, ’__delattr__’, ’__dict__’, ’__dir__’, ’__doc__’, ’__eq__’,3 ’__format__’, ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’, ’__init__’,4 ’__le__’, ’__lt__’, ’__module__’, ’__ne__’, ’__new__’, ’__reduce__’,5 ’__reduce_ex__’, ’__repr__’, ’__setattr__’, ’__sizeof__’, ’__str__’,6 ’__subclasshook__’, ’__weakref__’]

In the above example we altered an instance x of X externally, i.e., outside of the constructor,adding methods and instance variables. Can this be done with instances of the built-in classes list,set and so on? The answer is no.

1 >>> lst = [1,2,3]2 >>> lst.x = 53 Traceback (most recent call last):4 File "<stdin>", line 1, in <module>5 AttributeError: ’list’ object has no attribute ’x’6 >>> s = {1,2,3}7 >>> s.x = 58 Traceback (most recent call last):9 File "<stdin>", line 1, in <module>

10 AttributeError: ’set’ object has no attribute ’x’

The data attributes initialised within __init__() (and also attributes introduced in the mannerlike data and g) are instance variables whose value is unique per instance. In § 9.2 we shall encounterclass attributes which are shared across all instances of the given class.

Let us use the class Process defined above; this we write in file.py in the same directory asProcess1.py.

1 #!/usr/bin/env python32 from Process1 import Process34 print ("\nProcess 1:\n")5 process1 = Process("Printer A","Aug 8, 21:36","","123:90:34:90",0)6 process1.representation()7 process1.terminate("Aug 8, 21:37")8 process1.representation()9

1011 print ("\nProcess 2:\n")12 process2 = Process()13 process2.representation()14 process2 = process115 process2.representation()1617 # no need for __init()__18 print ("\nProcess 3: \n")19 process3 = Process()20 process3.name = "Printer B"21 process3.start = process1.end22 process3.representation()

41

Page 42:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Each class defines its own namespace which follows the name of the file containing the class;hence the import line at the top (in this example both file.py and Process1.py are at thesame directory).

§9.1 Private and public members

In Python there is no way to prevent external code from accessing the data of a class instance,as we saw with instance process3 in the above example. Python does provide a mechanism calledname mangling demonstrated next in order to provide a basic level of data encapsulation. Let ushave a Demo.py file with the following class X.

1 #class X23 class X:45 def __init__(self):6 self.public = "I am public"7 self.__private = "I am private"

Now in file.py we write1 from Demo import X23 x = X()456 print (x.public)78 print (x.__private)9 ------------------------------------

10 Output:1112 I am public13 Traceback (most recent call last):14 File "/Users/iElad/Desktop/Python/file.py", line 8, in <module>15 print (x.__private)16 AttributeError: X instance has no attribute ’__private’

Python reports that __private is not a member of class X. This is because Python altered ormangled the name of the variable; in particular it altered it to _X__private as can be seen fromthe following.

1 #!/usr/bin/env python323 #class X in Demo.py45 class X:67 def __init__(self):8 self.public = "I am public"9 self.__private = "I am private"

1011 def printPrivate(self):12 print (self.__private)1314 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^15 # file.py16 #!/usr/bin/env python3

42

Page 43:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1718 from Demo import X1920 x = X()212223 print (x.public)2425 print (x._X__private)2627 x._X__private = 1902822829 x.printPrivate()30 ---------------------------------------31 Output:3233 I am public34 I am private35 190282

All "private" attributes (data and method) will have their name altered to _classname__attrname.Name mangling is helpful for letting subclasses override methods without breaking intraclass methodcalls.

An alternative way to learn about the existence of this mechanism is Python is through dir().1 >>> class X:2 ... def __init__(self):3 ... self.public = "I am public"4 ... self.__private = "I am private"5 ...6 >>> dir(X())7 [’_X__private’, ’__class__’, ’__delattr__’, ’__dict__’, ’__dir__’, ’__doc__’,8 ’__eq__’, ’__format__’, ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’,9 ’__init__’, ’__le__’, ’__lt__’, ’__module__’, ’__ne__’, ’__new__’,

10 ’__reduce__’, ’__reduce_ex__’, ’__repr__’, ’__setattr__’, ’__sizeof__’,11 ’__str__’, ’__subclasshook__’, ’__weakref__’, ’public’]

43

Page 44:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

§9.2 Destructors

Classes may contain methods for cleanup purposes. Notice here that cnt is not introduced inthe constructor. In fact, cnt is a class attribute shared amongst all instances of class Point.

1 #!/usr/bin/env python323 #Demo.py456 class Point:78 cnt = 0 # class attribute9

10 def __init__(self, x = 0, y =0):11 self.setX(x) #delegate variable creation to other methods12 self.setY(y)13 Point.cnt+=114 print ("%d points created" % Point.cnt)1516 def setX(self,x):17 self.x =x1819 def setY(self,y):20 self.y = y2122 def __del__(self):23 Point.cnt-=124 print ("%d points left" % Point.cnt)2526 def f(self):27 print ("%d from f" % self.cnt) # is this allowed?

We shall now consider several examples all written in file.py in the same directory as Demo.py.1 #!/usr/bin/env python323 #file.py45 from Demo import Point67 p1 = Point()8 p2 = Point(2,4)9 p3 = Point(1,1)

1011 --------------------------------------------------------------------12 Output:1314 1 points created15 2 points created16 3 points created17 2 points left18 1 points left19 0 points left

Next consider the following.1 #file.py23 from Demo import Point45 p1 = Point()6 p1 = Point()7 p1 = Point()

44

Page 45:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

8 -----------------------------------------------------------------9 Output:

1011 1 points created12 2 points created13 1 points left14 2 points created15 1 points left16 0 points left

Finally lets invoke f.1 #file.py23 from Demo import Point456 Point(2,4).f()7 ---------------------------------------------------------------8 Output:9

10 1 points created11 1 from f12 0 points left

What is then the difference between cnt and the variables x and y? Let us consider a simplerexample.

1 #!/usr/bin/env python3234 class X:56 classVar = 078 def f1(self):9 self.classVar+=1

1011 def f2(self):12 X.classVar +=1131415 x =X()1617 print("x.classVar is X.classVar ?", x.classVar is X.classVar)1819 print("Apply f1")20 x.f1()21 x.f1()22 x.f1()2324 print("x.classVar is X.classVar ?", x.classVar is X.classVar)25 print("Through x:", x.classVar)26 print("Through X:", X.classVar)2728 print("Apply f2")29 x.f2()3031 print("x.classVar is X.classVar ?", x.classVar is X.classVar)32 print("Through x:", x.classVar)33 print("Through X:", X.classVar)34 -------------------------------------------------------35 Output:36

45

Page 46:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

37 x.classVar is X.classVar? True38 Apply f139 x.classVar is X.classVar? False40 Through x: 341 Through X: 042 Apply f243 x.classVar is X.classVar? False44 Through x: 345 Through X: 1

Prior to the invocations of f1() the variables x.classVar and X.classVar were the sameobject. After invoking f1() we see that these became two separate variables.

In the previous example the class attribute was an immutable object. Consequently, when the lineself.classVar+=1 is executed a new variable is created as the existing one cannot be changed.This means that the reference self.classVar is overridden with a new immutable object; however,the reference X.classVar remains the same. Let us demonstrate what has just happened usingthe following simpler code. Let us call self.classVar by the name x and X.classVar by thename of y.

1 >>> x = y =32 >>> x is y3 True4 >>> x+=15 >>> x is y6 False7 >>> x8 49 >>> y

10 3

Initially x and y are the same object; then x is overridden as a result of the assignment += separatingit from y.

For mutable class attributes the behaviour is different which is to be expected; we see this here.1 #!/usr/bin/env python3234 class C:56 classlist = [] # an empty list; class attribute78 def __init__(self, data = 0):9 self.data = data #instance attribute

1011 def addToList(self, x):12 self.classlist.append(x)1314 # main program1516 c = C()1718 print ("c.classlist is C.classlist?" , c.classlist is C.classlist)19 c.addToList("added through c")20 print ("c.classlist is C.classlist?" , c.classlist is C.classlist)21 C.classlist.append("added through C")22 print ("c.classlist is C.classlist?" , c.classlist is C.classlist)23 ---------------------------------------------------------------------------24 Output:25

46

Page 47:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

26 c.classlist is C.classlist? True27 c.classlist is C.classlist? True28 c.classlist is C.classlist? True

Here throughout and no matter how we altered classlist the variable c.classlist andC.classlist remain pointing to the same object. This is because we have used append()which altered classList internally causing no override of any reference as with immutable ob-jects. (Note that replacing self.classlist.append(x) with self.classlist+=[x] willnot change this behaviour).

This behaviour then for mutable class attributes explains the following perfectly.1 #!/usr/bin/env python3234 class C:56 classlist = [] # an empty list; class attribute78 def __init__(self, data = 0):9 self.data = data #instance attribute

1011 def addToList(self, x):12 self.classlist.append(x)1314 # main program1516 c1 = C(1)17 c2 = C(2)1819 c1.addToList("added through c1")20 c2.addToList("added through c2")2122 print (c1.classlist)2324 -----------------------------------------------------------------25 Output:2627 [’added through c1’, ’added through c2’]

Let us summarise then. A mutable class attribute behaves like a static variable as in C/C++,retaining its recent value resulting from the most recent reference of it. Immutable class attributesdo not offer this behaviour since whenever we seek to change them it is not the case that the object"recalls" the last change; in this case the object is overridden with a new object reflecting the mostrecent change.

§9.3 Inheritence

Python supports multiple inheritance. In what follows we define three classes all in the samefile.

1 #!/usr/bin/env python323 #file.py456 class Base1:

47

Page 48:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

78 def __init__(self, data = 0):9 self.data1 = data

1011 def present1(self):12 print ("\nPresent 1:", self.data1)131415 class Base2:1617 def __init__(self,data =0):18 self.data2 = data1920 def present2(self):21 print ("\nPresent 2:", self.data2)2223 class Derived(Base1, Base2): #declare 2 base classes; duplicates forbidden!2425 def __init__(self, data1 =0, data2 =0):26 Base1.__init__(self,data1) #init the bases27 Base2.__init__(self,data2)2829 def present3(self):30 print ("\nPresent 3:")31 self.present1()32 self.present2()3334 # main program3536 d = Derived(100,200)37 d.present1()38 d.present2()39 d.present3()40 --------------------------------------------41 Output:4243 Present 1: 1004445 Present 2: 2004647 Present 3:4849 Present 1: 1005051 Present 2: 200

When we examine the instance attributes of an instance of Derived we get the following.1 print(dir(Derived()))2 --------------------------------3 Output:45 [’__class__’, ’__delattr__’, ’__dict__’, ’__dir__’, ’__doc__’, ’__eq__’,6 ’__format__’, ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’, ’__init__’,7 ’__le__’, ’__lt__’, ’__module__’, ’__ne__’, ’__new__’, ’__reduce__’,8 ’__reduce_ex__’, ’__repr__’, ’__setattr__’, ’__sizeof__’, ’__str__’,9 ’__subclasshook__’, ’__weakref__’, ’data1’, ’data2’, ’present1’,

10 ’present2’, ’present3’]

Here, Base1 and Base2 define instance variables names data1 and data2 so there is a clearseparation. What is those names were to collide?

1 #!/usr/bin/env python32

48

Page 49:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

34 class Base1:56 def __init__(self, data = 0):7 self.data= data89 def present1(self):

10 print ("\nPresent 1:", self.data)111213 class Base2:1415 def __init__(self,data =0):16 self.data = data1718 def present2(self):19 print ("\nPresent 2:", self.data)2021 class Derived(Base1, Base2): #declare 2 base classes2223 def __init__(self, data1 =0, data2 =0):24 Base1.__init__(self,data1) #init the bases25 Base2.__init__(self,data2)2627 def present3(self):28 print ("\nPresent 3:")29 self.present1()30 self.present2()

Now both base classes define data as an instance variable. Examining the attributes of an instanceof Derived we get the following.

1 [’__class__’, ’__delattr__’, ’__dict__’, ’__dir__’, ’__doc__’, ’__eq__’,2 ’__format__’, ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’, ’__init__’,3 ’__le__’, ’__lt__’, ’__module__’, ’__ne__’, ’__new__’, ’__reduce__’,4 ’__reduce_ex__’, ’__repr__’, ’__setattr__’, ’__sizeof__’, ’__str__’,5 ’__subclasshook__’, ’__weakref__’, ’data’, ’present1’, ’present2’,6 ’present3’]

Now only the name data appears (as expected); which one though?1 d = Derived(100,300)2 print(d.data)3 --------------------------------------4 Output:56 300

The second base class "prevails". This is because in the constructor __init__() of Derived, theconstructor of Base2 is executed last so it is the last to make an assignment to the data attribute.

Next, let us consider having present1() and present2() collide.1 #!/usr/bin/env python3234 class Base1:56 def __init__(self, data = 0):7 self.data1= data89 def present(self):

10 print ("\nPresent 1:", self.data1)

49

Page 50:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

111213 class Base2:1415 def __init__(self,data =0):16 self.data2 = data1718 def present(self):19 print ("\nPresent 2:", self.data2)2021 class Derived(Base1, Base2): #declare 2 base classes2223 def __init__(self, data1 =0, data2 =0):24 Base1.__init__(self,data1) #init the bases25 Base2.__init__(self,data2)2627 # main program28 Derived(100,300).present()29 ----------------------------------------------------30 Output:3132 Present 1: 100

Here Python delegates the call to present() through an instance of Derived to the present()method of the first base class. Indeed, changing the order of inheritance changes the output asfollows.

1 #!/usr/bin/env python3234 class Base1:56 def __init__(self, data = 0):7 self.data1= data89 def present(self):

10 print ("\nPresent 1:", self.data1)111213 class Base2:1415 def __init__(self,data =0):16 self.data2 = data1718 def present(self):19 print ("\nPresent 2:", self.data2)2021 class Derived(Base2, Base1): #declare 2 base classes2223 def __init__(self, data1 =0, data2 =0):24 Base1.__init__(self,data1) #init the bases25 Base2.__init__(self,data2)2627 # main program28 Derived(100,300).present()29 ----------------------------------------------------30 Output:3132 Present 2: 300

We may get a report as to the base classes of each class using the __bases__ class attribute.1 print ("Bases of Derived:", Derived.__bases__)2 print ("Bases of Base1:", Base1.__bases__)

50

Page 51:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

3 print ("Bases of Base2:", Base2.__bases__)4 -----------------------------------------------------------------5 Output:67 Bases of Derived: (<class ’__main__.Base1’>, <class ’__main__.Base2’>)8 Bases of Base1: (<class ’object’>,)9 Bases of Base2: (<class ’object’>,)

We see that class object is a base class of all classes in our example. Indeed, every class in Pythoninherits from object.

1 >>> int.__bases__2 (<class ’object’>,)3 >>> dict.__bases__4 (<class ’object’>,)5 >>> list.__bases__6 (<class ’object’>,)7 >>> tuple.__bases__8 (<class ’object’>,)

Now it is clear where all the attributes of the form __<attr>__ came from.1 >>> o = object()2 >>> o3 <object object at 0x1003b7070>4 >>> dir(o)5 [’__class__’, ’__delattr__’, ’__dir__’, ’__doc__’, ’__eq__’, ’__format__’,6 ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’, ’__init__’, ’__le__’,7 ’__lt__’, ’__ne__’, ’__new__’, ’__reduce__’, ’__reduce_ex__’,8 ’__repr__’, ’__setattr__’, ’__sizeof__’, ’__str__’, ’__subclasshook__’]

The class Derived inherited the methods present1() and present2(). This next exampleshow cases how to override methods of a base class. In particular, we demonstrate this by overridingthe __str__()method inherited from class object in order to handle custom string representationfor custom class object.

1 #!/usr/bin/env python323 #file.py456 class Base:78 def __init__(self, data = 0):9 self.data = data

1011 def __str__(self):12 return "%d from Base" % self.data131415 class Derived(Base ):1617 def __init__(self, data =0):18 Base.__init__(self,data)1920 def __str__(self): #override21 return "%d from Derived" % self.data2223 # main program

51

Page 52:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

2425 d = Derived(200)2627 # invoke __str__ several times2829 print (d) # implicit call30 print (d.__str__()) # explicit call31 print (Derived.__str__(d))

Attempting to print a class instance for which __str__() is not override results in the following.1 >>> class X:2 ... pass3 ...4 >>> x =X()5 >>> x6 <__main__.X object at 0x10073a780>

§9.4 The diamond problem

Consider the following code that embodies the so called diamond problem in inheritance and themanner in which Python resolves it.

1 #!/usr/bin/env python3234 class A:56 def f(self):7 print("hello A")89

10 class B(A):1112 def f(self):13 print("hello B")1415 class C(A):1617 def f(self):18 print("hello C")1920 class D(B,C):21 pass2223 class E(C,B):24 pass2526 #main program2728 d = D()29 e = E()30 d.f()31 e.f()32 -------------------------------------------------33 Output:3435 hello B36 hello C

52

Page 53:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

The order in which the base classes are declared determines how the (virtual) method invocationswill be resolved. The search for the method to be invoked starts at the first base class listed. Letus now see what happened if class B does not define a method f while class C does. What would bethe result of d.f()

1 #!/usr/bin/env python323 class A:45 def f(self):6 print("hello A")789 class B(A):

1011 pass1213 class C(A):1415 def f(self):16 print("hello C")1718 class D(B,C):19 pass2021 #main program2223 d = D()24 d.f()25 -------------------------------------------------26 Output:2728 hello C

We see now that d.f() is the method from class C. This means that Python notices that B didnot define an f method and continues the search in the next base class.Put another way Pythonconducts a breadth first search in order to find the method to invoke.

§9.5 Abstract classes

To define abstract classes we shall use the exceptions mechanism provided by Python to bothmark an unimplemented method and to prevent creation of an instance of an abstract class. in§ 10 we consider exceptions with more detail; here it is enough to know that exceptions are classesdesigned to mark errors and warnings.

Here are two classes (in the same file). AbsClass is the abstract class; it contains a single ab-stract method __str__() that upon invocation will raise the exception NotImplementedError.Another point to notice in AbsClass is that it does have its own data absData, yet in its con-structor there is an if clause that can only be fulfilled by classes deriving AbsClass. In addition,it has an ordinary implemented method f.

1 #!/usr/bin/env python323 #file.py45

53

Page 54:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

6 class AbsClass:78 def __str__(self): # the abstract method no implementation9 raise NotImplementedError

1011 def __init__(self,data1 = 0,data2 = 0):1213 self.absData = data11415 if self.__class__ == AbsClass: # prevent creation16 raise NotImplementedError17 else:18 self.concreteData = data21920 def f(self,data): # an implemented method21 return data *self.absData222324 class Derived(AbsClass):2526 def __init__(self,data1,data2,data3):27 AbsClass.__init__(self,data1,data2) # call base class constructor28 self.moreConcreteData = data32930 def __str__(self): # override and implement31 return "abs base data = %d, concrete 1 = %d, concrete 2 = %d" % (self.absData,32 self.concreteData, self.moreConcreteData)3334 def g(self,data):35 return self.f(data) * self.concreteData*self.moreConcreteData

With these two classes defined let us consider some sample code to understand their relationship.The following code will fail (in the same file with the class definitions).

1 #!/usr/bin/env python323 b = AbsClass(2)4 print (b.f(2))

Indeed the implicit call to AbsClass.__init__() raises an exception. The following does work.1 #!/usr/bin/env python323 d = Derived(2,3,4)4 print ("Derived instace:\n", d)5 print ("\n%d from d.g()" % d.g(5))6 --------------------------------------7 Output:89 Derived instace:

10 abs base data = 2, concrete 1 = 3, concrete 2 = 41112 120 from d.g()

§9.6 Class attributes and inheritance

We consider the manner in which class attributes are inherited. Observe the following code.1 #!/usr/bin/env python32

54

Page 55:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

3 class A:4 x =156 class B(A):7 pass89 class C(A):

10 pass1112 print(A.x is B.x is C.x)13 B.x =214 print(A.x is B.x is C.x)15 ----------------------------------------16 Output:1718 True19 False

By changing the class attribute of B we have set it apart.1 print(A.x is C.x)2 print(A.x is B.x)3 --------------------------------4 Output:56 True7 False

In Python, class variables are internally handled as dictionaries and follow what is often referredto as Method Resolution Order. So in the above code, since the attribute x is not found in class C, itwill be looked up in its base classes. In other words, C does not have its own x property, independentof A. Thus, references to C.x are in fact references to A.x.

The issue here with B.x becoming a different variable is because x is immutable; this behaviourwill not occur if x is mutable.

§9.7 Bound and unbound methods

Up to Python 3 we distinguished between two types of class method. Those which are bound toa specific class instance and those that are unbound to any instance. Let us consider the followingexample.

1 #!/usr/bin/env python323 class X:456 def g(self):7 pass89 print(X().g)

10 print(X.g)11 ---------------------------------------------------------12 Output:1314 <bound method X.g of <__main__.X object at 0x10054b278>>15 <function X.g at 0x100662d08>

55

Page 56:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

The code here showcases two distinct functions X().g and X.g; note the different addressesthat these two have. Moreover, X.g and X().g are instances of two different classes:

1 #!/usr/bin/env python3234 class X:56 def g(self):7 pass89 print(type(X().g))

10 print(type(X.g))11 ------------------------------------------12 Output:1314 <class ’method’>15 <class ’function’>

The difference is that X().g is bound to the instance generated by the invocation of X(). On theother hand X.g is unbound to any instance of X. Unbound functions do not get a self parameterpassed to them implicitly hence the following.

1 #!/usr/bin/env python323 X.g()4 ---------------------------------------------------5 Output:67 TypeError: g() missing 1 required positional argument: ’self’

To invoke X.g we must pass it an instance of X explicitly, for instance, X.g(X()).In Python 3 unbound methods were conceptually deprecated and are now simply referred to as

functions. Put another way, in Python 2.x three types of functions existed: "regular" functions,bound class methods, and unbound class methods. For simplification reasons, Python 3.x onlysupports the former two versions.

We have met unbound methods already in our previous examples. Recall that we had invocationsof the form BaseClass.__init__(self) when initialising the inherited part of derived classes.

The ability to invoke class methods through the class name in a manner unbound to any classinstance (as though those were regular functions) is what separates the notion of self of Pythonfrom the notion of this in Java. Indeed, this in Java is always a reference to the current objectin which we are working in. But the call Base1.__init__(self) in the code above passes areference to of an instance of Derived to a method of Base1. In this kind of invocation self isnot similar to this.

§9.8 Static methods

To invoke class methods (bound or unbound) one must instantiate the class. For reasons ofdesign and performance there are occasions where one would like to avoid that. Class methods thatdo not require an actual instance of the class to be invoked are called static. To write static methodsomit the self parameter.

56

Page 57:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 #!/usr/bin/env python32 #file.py34 class StaticExample:56 instanceCnt = 078 #this is a static function9 def getCnt_static():

10 return StaticExample.instanceCnt1112 def getCnt(self):13 return StaticExample.instanceCnt1415 def __del__(self):16 StaticExample.instanceCnt -= 117 print ("%d instances left" % StaticExample.instanceCnt)181920 def __init__(self):21 StaticExample.instanceCnt+=122 print ("%d instances created" % StaticExample.instanceCnt)2324 # main program2526 print ("%d instances now" % StaticExample.getCnt_static())27 -----------------------------------------------------------------------------------28 Output:2930 0 instances now

We are not able to invoke a static method through an instance.1 #!/usr/bin/env python323 s = StaticExample()4 print(s.getCnt_static())5 ----------------------------------------------------------------------------------6 Output:78 1 instances created9 Traceback (most recent call last):

10 File "/Users/iElad/Dropbox/Teaching/Afeka/PPL/Material/Python/Demo.py", line 25, in <module>11 print(s.getCnt_static())12 TypeError: getCnt_static() takes 0 positional arguments but 1 was given13 0 instances left

Suppose now that we are considering to have an alternative to using static methods, and in ourexample we seek to use the getCnt() instead of getCnt_static(). The point being here is thatwe are ignoring self in getCnt(). The effect is different.

1 #!/usr/bin/env python32 #file.py34 print (StaticExample().getCnt())5 ---------------------------------------------------------------------------------6 Output:78 1 instances created9 0 instances left

10 1

57

Page 58:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

The first line in the output is produced when print() in __init__() is invoked. Then, getCnt()returns 1, followed by the print() invocation of __del__(). Finally, the "global" print() iscarried out and it prints 1, the value returned from getCnt().

We have seen that it is not necessary that the definition of a method will be textually enclosedwithin the class definition: as functions are objects, assigning a function object to a local variablein the class is also permitted.

1 #!/usr/bin/env python323 def f(x):4 print(x)56 class X:7 g = f

The question now is should we consider the method g as static. On the one hand it is not passedself reference (indeed, f is a global function unbound to any class instance). On the other handwe may invoke g through an instance of X, that is we may write X().g("hello") for instance.

§9.9 Class methods and variables having the same names

Let us try to determine what is f in the following code, is it an int object or is it a function?1 #!/usr/bin/env python3234 class C:56 def __init__(self, f ):78 self.f = f9

10 def f(self, x):11 print(x)1213 #main program1415 c =C(1)1617 c.f()18 ----------------------------------------------------19 Output:2021 Traceback (most recent call last):22 File "/Users/iElad/Dropbox/Teaching/Afeka/PPL/Material/Python/file.py", line 17, in <module>23 c.f()24 TypeError: ’int’ object is not callable

At the invocation time of C() the assignment self.f =1 is carried out overriding the definitionof f as a function.

§9.10 Static variables for functions and methods

58

Page 59:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

In the previous section we saw that attributes can be added to an instance of a class externallyto the definition of the class. Recall that in Python function are objects and in particular instancesof class function. We may add attributes to those objects as well. This way we can create amechanism that has the same effect as a static variable for functions known in languages such asC/C++.

1 #!/usr/bin/env python323 def f(y):4 f.x += y5 print(f.x)67 f.x = 0 # initialise the "static" variable89

10 f(5)11 f(5)12 f(5)13 -----------------------------1415 Output:1617 518 1019 15

Here is an example where the "static" variable is a mutable object.1 def f(y):2 f.x.append(y)3 print(f.x)45 f.x = []678 f(1)9 f(2)

10 f(3)11 -----------------------------12 Output:1314 [1]15 [1, 2]16 [1, 2, 3]

We have seen in § 9.7 that unbound methods are instances of class function (just like regularfunctions). Hence, adding "static" variables to those can be done in the same way as this was donefor functions.

1 #!/usr/bin/env python3234 class X:56 def g(self,y):7 self.g.x.append(y)8 print(self.g.x)9

1011 X.g.x = []12

59

Page 60:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

13 X.g(X(),1)14 X.g(X(),2)15 X.g(X(),3)1617 ----------------------------------18 Output:1920 [1]21 [1, 2]22 [1, 2, 3]

Note that we have defined X.g.x =[] yet in g we referenced the list x through self and notthrough X. As x is a mutable object there is no danger. It would more desirable to replace self.g.xwith X.g.x.

Next, lets consider how to add "static" variables to bound methods. Our approach from abovedoes not work now.

1 #!/usr/bin/env python3234 class X:56 def g(self,y):7 self.g.x.append(y)8 print(self.g.x)9

1011 x = X()1213 x.g.x = []14 -------------------------------------15 Output:1617 Traceback (most recent call last):18 File "/Users/Elad/Dropbox/Teaching/Afeka/PPL/Material/Python/file22.py", line 13, in <module>19 x.g.x = []20 AttributeError: ’method’ object has no attribute ’x’

Unlike class function, class method does not allow us to add attributes to its instancesexternally. A way around this is to "sacrifice" a class attribute for the sake of a static variable fora bound instance of g.

1 #!/usr/bin/env python3234 class X:56 static_list_for_g = []78 def g(self,y):9 self.static_list_for_g.append(y)

10 print(self.static_list_for_g)111213 x = X()1415 x.g(1)16 x.g(2)17 x.g(3)18 ----------------------------------------------19

60

Page 61:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

20 Output:2122 [1]23 [1, 2]24 [1, 2, 3]

§10 Exceptions

To mark that certain events have taken place during execution time, Python supports the mech-anism of exceptions. Python provides an elaborate system of classes to indicate exceptions.

Python uses try statements to enable exception handling. The try statement may specifyone or more except clauses that immediately follow the try suite. Each except clause specifieszero or more exception class names that represent the type(s) of exceptions that the except clausecan handle. An except clause (also called an except handler) also may specify an identifier thatthe program can use to reference the exception object that was caught. The handler can use theidentifier to obtain information about the exception from the exception object. An except clausethat specifies no exception type is called an empty except clause. Such a clause catches all exceptiontypes. After the last except clause, an optional else clause contains code that executes if thecode in the try suite raised no exceptions.

1 #!/usr/bin/env python3234 number1 = input( "Enter numerator: " )5 number2 = input( "Enter denominator: " )67 try:8 number1 = float( number1 )9 number2 = float( number2 )

10 result = number1 / number21112 # float raises a ValueError exception13 except ValueError:14 print ("You must enter two numbers")1516 # division by zero raises a ZeroDivisionError exception17 except ZeroDivisionError:18 print ("Attempted to divide by zero")1920 # else clause’s suite executes if try suite raises no exceptions21 else:22 print ("%.3f / %.3f = %.3f" % ( number1, number2, result ))

If a try statement specifies no except clauses, the statement must contain a finally clause, whichalways executes, regardless of whether an exception occurs. There cannot be try statements withexcept clauses and a finally clause.

1 #!/usr/bin/env python3234 try:5 pass67 finally:

61

Page 62:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

8 print ("Finally always executes")

To explicitly raise an exception we use the raise statement.1 #!/usr/bin/env python323 try:4 raise Exception("Problem occurred")56 except Exception as e:7 print(e)8 raise # in case of partial handling re-raise the exception

Here, the except clause labelled the caught exception as e.Python supports the following exception classes; here is their hierarchy.

1 BaseException2 ... Exception3 ...... StandardError4 ......... TypeError5 ......... ImportError6 ............ ZipImportError7 ......... EnvironmentError8 ............ IOError9 ............... ItimerError

10 ............ OSError11 ......... EOFError12 ......... RuntimeError13 ............ NotImplementedError14 ......... NameError15 ............ UnboundLocalError16 ......... AttributeError17 ......... SyntaxError18 ............ IndentationError19 ............... TabError20 ......... LookupError21 ............ IndexError22 ............ KeyError23 ............ CodecRegistryError24 ......... ValueError25 ............ UnicodeError26 ............... UnicodeEncodeError27 ............... UnicodeDecodeError28 ............... UnicodeTranslateError29 ......... AssertionError30 ......... ArithmeticError31 ............ FloatingPointError32 ............ OverflowError33 ............ ZeroDivisionError34 ......... SystemError35 ............ CodecRegistryError36 ......... ReferenceError37 ......... MemoryError38 ......... BufferError39 ...... StopIteration40 ...... Warning41 ......... UserWarning42 ......... DeprecationWarning43 ......... PendingDeprecationWarning44 ......... SyntaxWarning45 ......... RuntimeWarning46 ......... FutureWarning47 ......... ImportWarning48 ......... UnicodeWarning

62

Page 63:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

49 ......... BytesWarning50 ...... _OptionError51 ... GeneratorExit52 ... SystemExit53 ... KeyboardInterrupt

To handle multiple exception types in a single except clause, use the syntax:

except (ExceptionType1,ExceptionType1,....):

All (custom) exception types must be derived from BaseException.

§11 Iterators and generators

Throughout we have encountered various container-type structures such as lists, strings, tuples,sets, and dictionaries. For each we had a concise way of iterating through them using a for loop.

1 for element in [1, 2, 3]:2 print(element)3 for element in (1, 2, 3):4 print(element)5 for key in {’one’:1, ’two’:2}:6 print(key)7 for char in "123":8 print(char)9 for line in open("myfile.txt"):

10 print(line, end=’’)

The notion of iterators is what makes such statements work. The for statement calls iter() onthe container object. The function returns an iterator object that defines the method __next__()which accesses elements in the container one at a time. When there are no more elements, __next__()raises a StopIteration exception which tells the for loop to terminate. We may call the__next__() method using the next() built-in method.

1 >>> s = ’abc’2 >>> it = iter(s)3 >>> it4 <iterator object at 0x00A1DB50>5 >>> next(it)6 ’a’7 >>> next(it)8 ’b’9 >>> next(it)

10 ’c’11 >>> next(it)12 Traceback (most recent call last):13 File "<stdin>", line 1, in ?14 next(it)15 StopIteration

With this explanation of how a for loop operates in Python we may understand the followingcode.

1 #!/usr/bin/env python32

63

Page 64:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

3 for i in range(10):4 print (i),5 i = i+10006 ---------------------------------------7 Output:89 0

10 111 212 313 414 515 616 717 818 9

We see that the assignment i = i+1000 carried no effect as i travelled through the loop. Ananswer to this comes by first examining what is returned from range().

1 >>> type(range(10))2 <class ’range’>34 >>> dir(range(10))5 [’__class__’, ’__contains__’, ’__delattr__’, ’__dir__’, ’__doc__’, ’__eq__’,6 ’__format__’, ’__ge__’, ’__getattribute__’, ’__getitem__’, ’__gt__’,7 ’__hash__’, ’__init__’, ’__iter__’, ’__le__’, ’__len__’, ’__lt__’, ’__ne__’,8 ’__new__’, ’__reduce__’, ’__reduce_ex__’, ’__repr__’, ’__reversed__’,9 ’__setattr__’, ’__sizeof__’, ’__str__’, ’__subclasshook__’,

10 ’count’, ’index’, ’start’, ’step’, ’stop’]

We see that range() returns an object of a class named range. We use the dir() function tolearn about the attributes of this object. This object is an iterable object and thus it is now clearwhy the output of the above snippet is as it is.

Let us now add iterator behaviour to a class. We define an __iter__() method which returnsan object with a __next__() method. If the class defines __next__(), then __iter__() canjust return self.

1 class Reverse:2 """Iterator for looping over a sequence backwards."""3 def __init__(self, data):4 self.data = data5 self.index = len(data)6 def __iter__(self):7 return self8 def __next__(self):9 if self.index == 0:

10 raise StopIteration11 self.index = self.index - 112 return self.data[self.index]

Let us use our new class.1 >>> rev = Reverse(’spam’)2 >>> iter(rev)3 <__main__.Reverse object at 0x00A1DB50>4 >>> for char in rev:5 ... print(char)6 ...7 m

64

Page 65:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

8 a9 p

10 s

Generators are a simple and powerful tool for creating iterators. They are written like regularfunctions but use the yield statement instead of return whenever they want to return data.Each time next() is called on it, the generator resumes where it left-off (it remembers all the datavalues and which statement was last executed).

1 def myReverse(data):2 for index in range(len(data)-1, -1, -1):3 yield data[index]

We use this generator as follows.1 >>> for char in myReverse(’golf’):2 ... print(char)3 ...4 f5 l6 o7 g

Anything that can be done with generators can also be done with class based iterators as de-scribed in the previous section. What makes generators so compact is that the __iter__() and__next__() methods are created automatically.

Another key feature is that the local variables and execution state are automatically savedbetween calls. This made the function easier to write and much more clear than an approach usinginstance variables like self.index and self.data.

In addition to automatic method creation and saving program state, when generators terminate,they automatically raise StopIteration. In combination, these features make it easy to createiterators with no more effort than writing a regular function.

Some simple generators can be coded succinctly as expressions using a syntax similar to list com-prehensions but with parentheses instead of brackets. These expressions are designed for situationswhere the generator is used right away by an enclosing function. Generator expressions are morecompact but less versatile than full generator definitions and tend to be more memory friendly thanequivalent list comprehensions.

1 >>> sum(i*i for i in range(10)) # sum of squares2 28534 >>> xvec = [10, 20, 30]5 >>> yvec = [7, 5, 3]6 >>> sum(x*y for x,y in zip(xvec, yvec)) # dot product7 26089 >>> from math import pi, sin

10 >>> sine_table = {x: sin(x*pi/180) for x in range(0, 91)}1112 >>> valedictorian = max((student.gpa, student.name) for student in graduates)1314 >>> data = ’golf’15 >>> list(data[i] for i in range(len(data)-1, -1, -1))16 [’f’, ’l’, ’o’, ’g’]

65

Page 66:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

§12 Custom arrays

Many of the data types and classes available in Python are actually implemented using appro-priate types from the C language. While Python does not provide the array structure as part of thelanguage itself, it now includes the ctypes module as part of the Python Standard Library. Thismodule provides access to the diverse set of data types available in the C language and the completefunctionality provided by a wide range of C libraries.

The ctypes module provides the capability to create hardware-supported arrays just like theones used to implement a Python string, list, tuple, and dictionary collection types. But the ctypesmodule is not meant for everyday use in Python programs as it was designed for use by moduledevelopers to aide in creating more portable Python modules by bridging the gap between Pythonand the C language. Much of the functionality provided by the ctypes module requires someknowledge of the C language. Thus, the technique provided by the module for creating an arrayshould not typically be used directly within a Python program.

1 import ctypes23 ArrayType = ctypes.py_object *3 # a type for arrays of size 345 theArray = ArrayType() # an instance of an array of size 367 for i in range(3): # initialise the array8 theArray[i] = i+1

Let us examine ArrayType.1 >>> import ctypes2 >>> ArrayType = ctypes.py_object *33 >>> x = ArrayType()45 >>> type(x)6 <class ’__main__.py_object_Array_3’>78 >>> dir(x)9 [’__class__’, ’__ctypes_from_outparam__’, ’__delattr__’, ’__delitem__’, ’__dict__’,

10 ’__dir__’, ’__doc__’, ’__eq__’, ’__format__’, ’__ge__’, ’__getattribute__’,11 ’__getitem__’, ’__gt__’, ’__hash__’, ’__init__’, ’__le__’, ’__len__’, ’__lt__’,12 ’__module__’, ’__ne__’, ’__new__’, ’__reduce__’, ’__reduce_ex__’, ’__repr__’,13 ’__setattr__’, ’__setitem__’, ’__setstate__’, ’__sizeof__’, ’__str__’,14 ’__subclasshook__’, ’__weakref__’, ’_b_base_’, ’_b_needsfree_’, ’_length_’,15 ’_objects’, ’_type_’]1617 >>> ArrayType.__bases__18 (<class ’_ctypes.Array’>,)

With this we may implement a class Array that is missing from Python.1 #!/usr/bin/env python323 import ctypes45 class Array :67 def __init__(self, size):8 assert size > 0, "Array size must be > 0"9 self._size = size

10 # create the underlying array

66

Page 67:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

11 ArrayType = ctypes.py_object *size12 self._elements = ArrayType()13 # initialise the array14 self.clear(None)151617 def __len__( self ):18 return self._size192021 def __getitem__( self, index ):22 assert index >= 0 and index < len(self), "Array subscript out of range"23 return self._elements[ index ]2425 def __setitem__(self,index,value):26 assert index >= 0 and index < len(self), "Array index out of range"27 self._elements[index] = value282930 def clear(self, value):31 for i in range(len(self)):32 self._elements[i] = value3334 def __str__(self):35 list = []36 for i in range(len(self)):37 list.append (self._elements[i])38 return list.__str__()3940 def __iter__(self):41 return _ArrayIterator(self._elements)424344 # An iterator for the Array ADT.45 class _ArrayIterator :4647 def __init__( self, theArray ):48 self._arrayRef = theArray49 self._curNdx = 05051 def __iter__( self ):52 return self5354 def __next__( self ):55 if self._curNdx < len( self._arrayRef ) :56 entry = self._arrayRef[ self._curNdx ]57 self._curNdx += 158 return entry59 else :60 raise StopIteration6162 # Main program6364 theArray = Array(10)65 for i in range (10):66 theArray[i] = 2*i67 print(theArray)

Several elements have been introduced in this program. First the assert statement. Thestatement reads assert <condition>, <message> and it is equivalent to

1 if not condition:2 raise AssertionError(message)

67

Page 68:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

The methods __len__(), __getitem__(), and __setitem__() are special functions thatmust be implemented in order for the Python mechanism behind len() and the [] referencebrackets to work.

§12.1 Matrices

Python does not directly support built-in arrays of any dimension. But, in the previous section,we were able to use the ctypes module to create a one-dimensional hardware-supported array; letus now do the same for matrices.

1 #!/usr/bin/env python323 import ctypes4 from ArrayMod import Array56 class Matrix:78 def __init__( self, numRows, numCols ):9 self._theRows = Array( numRows )

10 for i in range (numRows):11 self._theRows[i] = Array( numCols )12 self.clear(None)1314 def numRows( self ):15 return len( self._theRows )1617 def numCols( self ):18 return len( self._theRows[0] )1920 def clear(self, value):21 for row in self._theRows:22 row.clear(value)2324 def __getitem__(self,ndxTuple):25 assert len(ndxTuple) == 2, "Invalid number of indices"26 row = ndxTuple[0]27 col = ndxTuple[1]28 assert row>= 0 and row< self.numRows() \29 and col >=0 and col < self.numCols(), \30 "Array indices out of bounds"31 theRow = self._theRows[row]32 return theRow[col]3334 def __setitem__(self,ndxTuple,value):35 assert len(ndxTuple) == 2, "Invalid number of indices"36 row = ndxTuple[0]37 col = ndxTuple[1]38 assert row>= 0 and row< self.numRows() \39 and col >=0 and col < self.numCols(), \40 "Array indices out of bounds"41 theRow = self._theRows[row]42 theRow[col] = value4344 def __str__(self):45 list = []46 for row in self._theRows:47 for i in range(len(row)):48 list.append(row[i])49 return list.__str__()5051 # main program

68

Page 69:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

52 m = Matrix(2,2)53 m[0,0]=154 m[0,1] = 255 m[1,0] = 356 m[1,1] = 45758 print(m)

§13 Multithreading in Python

I. A single process in modern operating systems may have several threads of execution due to variousarising needs. The switching between the threads of execution of a single process is referred to asmultithreading. This need to switch between the various threads of a single process prompts thequestion of how to schedule these for execution as only one may run at a given moment. Indeed,multithreading is not parallel computing; here we often use the term pseudo-parallel execution orconcurrent execution. We shall not address the benefits of having multithreaded systems; suffice tosay that employing such paradigms increase the responsiveness of processes.

The threads of a given process share the resources supplied by the process; in particular itsmemory space. Consequently, various types of sharing violations may occur; leading to the need tosynchronise the threads of the given process. The purpose of this section is to overview some of themore classic mechanisms used to achieve this; in particular we shall see how these can be used inPython.

II. The life cycle of a thread is succinctly summarised in Figure 1.

Figure 1: Life cycle of a thread

69

Page 70:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

III. Printing illustration. To illustrate the need for thread synchronisation let us first considerthe following example of a single process in charge loading files onto a queue of a printer. A variablenext_free_slot is maintained to keep track which slot of the queue is currently free (assumearray implementation of the queue). Suppose further that the task of loading the queue with filesis performed by (a pool of) two threads. A thread with CPU attention retrieves addresses of filesfrom a given list and inserts them sequentially in the next_free_slot of the queue; the threadcontinues until the CPU attention is revoked from it by the scheduler.

Here is a likely scenario. Suppose thread A just finished loading a file into slot 4 and is about toincrement the variable next_free_slot to 5; however, prior to doing so the scheduler switches tothread B which continues its work of loading at the value indicated by next_free_slot which isstill 4. The result is that the last file loaded by thread A will not be printed.

IV. Race conditions. A program in which several threads are involved using shared data and theoutput of the program depends on the order in which these are executed is said to be a programthat contains race conditions; indeed, the threads race in a sense over the shared data to completetheir task.

Programs containing race conditions have unexpected executions and outputs; situation we wishto avoid.

V. Critical sections. To avoid race conditions one marks the critical sections of a multithreadedprogram; those are the sections of the program in which shared data is accessed. The synchronisationmechanisms we shall encounter below will allow only a single thread to execute within each suchsection. This will be attained through mutual exclusion.

§13.1 Creating threads in Python

The Python multithreading capabilities mainly lie within the threading module. Usually, onewould create threads by instantiating objects of class threading.Thread. For customisation weusually derive class threading.Thread. The bulk of the work of the thread is done in its run()method; which we override for our purposes.

With a subclass of threading.Thread ready, a program launches a thread by calling itsstart() method, which, in turn, calls the run() method. After start() launches the thread,start() returns to its caller immediately. The caller then executes concurrently with the launchedthread. If the thread has already been started, the start() method raises an AssertionErrorexception.

We review a few additional methods of threading.Thread. The method isAlive() returns1 if start() has been called for a given thread and the thread is not dead (i.e., its controlling runmethod has not completed execution). The method join() waits for the thread whose join()method is called to die before the caller can proceed. A thread may not call its own join() method,only that of other threads; however, it may be join()ed many times. An optional argument

70

Page 71:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

accepted by join() is a timeout, a floating-point number specifying the number of seconds thatthe caller waits. Passing no argument to method join indicates that the caller waits forever for thetarget thread to die before the caller proceeds.

There are also global functions that interact with threads. For instance, the global functionthreading.currentThread() returns a reference to the currently executing Thread. The func-tion threading.enumerate() returns a list of all currently executing Thread objects, includingthe main thread. The Function threading.activeCount() returns the length of the list re-turned by threading.enumerate(). The function time.sleep() sends the thread invokingit to sleep.

Here is a rudimentary example in which we subclass threading.Thread.1 #!/usr/bin/env python3234 import threading5 import random6 import time78 class CustomThread ( threading.Thread ):9

10 def __init__(self, threadName):11 threading.Thread.__init__(self,name = threadName)12 self.sleepTime = random.randrange(1,6)13 self.cnt = 014 print ("Name %s; sleep: %d" % (self.getName(), self.sleepTime))151617 def run(self):1819 while self.cnt < 2:20 print ("%s goes to sleep for the %d time" % \21 (self.getName(), self.cnt+1))22 self.cnt+=123 time.sleep(self.sleepTime)24 print("%s is done sleeping" % self.getName())252627 #main program2829 t1 = CustomThread ("t1")30 t2 = CustomThread ("t2")31 t3 = CustomThread ("t3")32 t4 = CustomThread ("t4")3334 t1.start()35 t2.start()36 t3.start()37 t4.start()

We do not necessarily need to subclass threading.Thread to create threads. For instance,suppose we have the following function.

1 def countdown(n):2 while n > 0:3 n -= 1

Now suppose we have a lot to count:1 COUNT = 100000000 # 100 million

71

Page 72:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

2 countdown(COUNT)

We can divide the work amongst two threads as follows.1 t1 = Thread(target=countdown,args=(COUNT//2,))2 t2 = Thread(target=countdown,args=(COUNT//2,))3 t1.start(); t2.start()4 t1.join(); t2.join()

Here the main thread, i.e., that of the interpreter is calling the method join() of t1. This willcause the main thread to block until t1 terminates.

Here is a summary of some of the global functions defined in threading to interact with threads.

threading.active_count() Return the number of Thread objects currently alive.threading.current_thread() Return the current Thread object, corresponding

to the caller’s thread of control.threading.get_ident() Return the "thread identifier" of the current thread.

This is a nonzero integer. Its value has no direct meaning;Thread identifiers may be recycled when a thread exits andanother thread is created.

threading.enumerate() Return a list of all Thread objects currently alive.threading.main_thread() Return the main Thread object.

Here is a summary of some of the methods of threading.Thread.

start() Start the thread’s activity.It must be called at most once per thread object.It arranges for the object’s run() method to be invoked in aseparate thread of control. Will raise a RuntimeError if calledmore than once on the same thread object.

run() Method representing the thread’s activity.join(timeout=None) see above.name A string used for identification purposes only.

It has no semantics.Multiple threads may be given the same name.The initial name is set by the constructor.

ident The "thread identifier" of this thread or Noneif the thread has not been started. This is a nonzero integer.

§13.2 GIL

The Python interpreter controls all threads in a program. When the interpreter starts, either inan interactive session or when invoked on a file, the main thread begins. This thread is the callerfor all other threads. Only one thread is permitted to run by the interpreter at any one time. The

72

Page 73:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

interpreter keeps track of the global interpreter lock (GIL) that controls which thread the interpreteris running. When a program contains more than one running thread, these threads are switched inand out of the interpreter through the GIL, at specified intervals.

In general, Python offers to switch among threads only between bytecode instructions; howfrequently it switches can be set via sys.setcheckinterval. Each bytecode instruction andtherefore all the C implementation code reached from each instruction is therefore atomic from thepoint of view of a Python program.

When a thread is running it holds the GIL. The GIL is released whenever the holding threadperforms I/O operations.

Figure 2: Threads in Python and the GIL

With the release of Python 3.2 a major update to the handling of threads in Python was in-troduced. In what follows we shall compare the handling of threads prior to and after the releaseof Python 3.2. The update was prompted by certain weird performance results. Indeed, prior toPython 3.2 the following sequential counting operation

1 COUNT = 100000000 # 100 million2 countdown(COUNT//2)3 countdown(COUNT//2)

performed significantly faster than the multithreaded counting we saw above.To understand the reason for this, let us first have a distinction between CPU bound threads

and I/O bound threads. The former perform mainly calculations on the CPU and rarely turn to I/Orequests; the latter are involved mainly with I/O operations. For instance, the counting threads wemet above are CPU bound thread. Indeed, given a thread there is no clear way to say wether it isCPU bound or I/O bound. For the sake of simplicity though of the following let us consider pureCPU bound threads that perform no I/O operations.

Prior to Python 3.2, at fixed intervals measured by ticks the interpreter pauses bytecode executionand checks whether any signal handlers have to be executed. The size of this interval in ticks was ob-tainable through sys.getcheckinterval() and could be set using sys.setcheckinterval(). So, if we have a pure CPU bound thread its execution will resemble that shown in FigureZ??.

73

Page 74:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Figure 3: Execution of a pure CPU bound thread in Python

What are ticks? Ticks loosely corresponded to bytecode instructions. Let us consider the functioncountdown() defined above and observe its bytecode; this we can obtain using the dis.dis()function.

1 >>> def countdown(n):2 ... while n >0:3 ... print(n)4 ... n-=15 ...6 >>> import dis7 >>> dis.dis(countdown)8 2 0 SETUP_LOOP 36 (to 39)9 >> 3 LOAD_FAST 0 (n)

10 6 LOAD_CONST 1 (0)11 9 COMPARE_OP 4 (>)12 12 POP_JUMP_IF_FALSE 381314 3 15 LOAD_GLOBAL 0 (print)15 18 LOAD_FAST 0 (n)16 21 CALL_FUNCTION 1 (1 positional, 0 keyword pair)17 24 POP_TOP1819 4 25 LOAD_FAST 0 (n)20 28 LOAD_CONST 2 (1)21 31 INPLACE_SUBTRACT22 32 STORE_FAST 0 (n)23 35 JUMP_ABSOLUTE 324 >> 38 POP_BLOCK25 >> 39 LOAD_CONST 0 (None)26 42 RETURN_VALUE

In Figure 4, we see a rough illustration of the correspondence between ticks and bytecode instructions.

The main message to gather from this illustration is that ticks need not have a uniform size.In Python 3.2 the mechanism for thread switching had been changed. Instead of ticks there is

now a global variable; in C this variable would look roughly like this:

static volatile int gil_drop_request = 0;

A thread runs until the value of this variable gets set to 1; at which point, the thread must dropthe GIL. The main advantage in this approach is that it allows threads to voluntarily give up theGIL and if not they respond to a signal and this is instead of full check of all signal handlers donebefore. We shall not provide additional details as to this change.

74

Page 75:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Figure 4: The "tick" measurement

§13.3 Atomic operations

The threadingmodule provides many thread synchronisation primitives such as Locks, RLocks,Events, Condition variables, and Semaphores. All these are implemented using the single lock-ing mechanism supplied by the interpreter. In particular, the GIL is an instance of this lockingmechanism.

Prior to the introduction of all these synchronisation mechanisms, let us motivate the need tosynchronise threads.

1 counter = 023 def f(x):4 global counter5 #do something with x6 counter += 1

In the code above we use the global variable counter to keep track of the number of times f() isinvoked. However, if one applies f() from more than one thread it can sometime be observed thatcounter "misses" invocations and counts incorrectly (that is, it counts less than it should).

The reason for this is that the increment operation is actually executed in three (bytecode) steps(see the code snippet below); first, the interpreter fetches the current value of the counter, then itcalculates the new value, and finally, it writes the new value back to the variable. If another threadgets control after the current thread has fetched the variable, it may fetch the variable, incrementit, and write it back, before the current thread does the same thing. And since they are both seeingthe same original value, only one item will be accounted for.

1 >>> import dis2 >>> def g(x):3 ... x+=14 ...

75

Page 76:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

5 >>> dis.dis(g)6 2 0 LOAD_FAST 0 (x)7 3 LOAD_CONST 1 (1)8 6 INPLACE_ADD9 7 STORE_FAST 0 (x)

10 10 LOAD_CONST 0 (None)11 13 RETURN_VALUE

Another common problem is access to incomplete or inconsistent state, which can happen if onethread is initialising or updating some non-trivial data structure, and another thread attempts toread the structure while it is being updated.

The simplest way to synchronise access to shared variables or other resources is to rely on atomicoperations in the interpreter. An atomic operation is an operation that is carried out in a singleexecution step, without any chance that another thread gets control. Making all operations atomicwould result in a language that does not support parallelism. Here are some thread-safe operationsin Python:

reading or replacing a single instance attributereading or replacing a single global variablefetching an item from a listmodifying a list in place (e.g. adding an item using append)fetching an item from a dictionarymodifying a dictionary in place (e.g. adding an item, or calling the clear method)

Note that as mentioned earlier, operations that read a variable or attribute, modifies it, and thenwrites it back are not thread-safe. Another thread may update the variable after it has been readby the current thread, but before it has been updated.

Here are more concrete examples of atomic and non-atomic operations in Python. The followingoperations are atomic:

1 L.append(x) # assume L is a list2 L1.extend(L2) # assume L1 is a list3 x = L[i]4 x = L.pop()5 L1[i:j] = L26 L.sort()7 x = y8 x.field = y9 D[x] = y #assume D is a dictionary

10 D1.update(D2) # assume D1 is a dictionary11 D.keys()

The following operations in Python are non-atomic:1 i = i+12 L.append(L[-1])3 L[i] = L[j]4 D[x] = D[x] + 1

76

Page 77:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

§13.4 Locks and re-entrant locks

§13.4.1 Locks

Perhaps the most primitive synchronisation mechanism is the lock. A lock is in one of two states,locked or unlocked. A lock object created with class threading.Lock is initially unlocked. Thisclass defines two methods: acquire() and release(). When a thread calls the acquire()method, the lock enters its locked state; the method returns immediately. The return value is Trueif the lock is acquired successfully, and False otherwise.

Once a lock has been acquired, no other threads may acquire the lock (including the thread thatalready acquired the lock; see details below); this until the lock is released. This means that if anythread (including the one that currently acquired the lock) calls the lock acquire() method, thethread will block indefinitely.

When the release() method is called the lock enters the unlocked state and some blockedthread is notified (awakened); the method returns immediately. If more than one thread is blockedon a lock, only one of those threads is notified. If an attempt is made to release an unlocked lock, aRuntimeError will be raised. This method has no return value.

Here is a traditional use of threading.Lock.1 from threading import Lock23 lock = Lock()45 lock.acquire() # will block if lock is already held6 #... access shared resource7 lock.release()

For proper operation, it is important to release the lock even if something goes wrong when accessingthe resource. You can use try-finally for this purpose:

1 lock.acquire()2 try:3 #... access shared resource4 finally:5 lock.release() # release lock, no matter what

The full signature of the acquire() method is acquire(blocking=True, timeout=-1).We may use the blocking flag to avoid blocking if the lock currently locked. That is, applyingacquire() with an argument only blocks if the argument is True and the return value reflectswhether the lock is acquired. For instance, if you pass in False, the method never blocks, butreturns False if the lock was already held:

1 if not lock.acquire(False):2 ... failed to lock the resource3 else:4 try:5 ... access shared resource6 finally:7 lock.release()

77

Page 78:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

You can use the locked() method to check if the lock is held. Note that you cannot usethis method to determine if a call to acquire() would block or not; some other thread may haveacquired the lock between the method call and the next statement.

1 if not lock.locked():2 # some other thread may run before we get3 # to the next line4 lock.acquire() # may block anyway

Locks can be used to restrict access to a critical section. The program below is written in such away that the thread must acquire a lock before entering a critical section and release the lock whenexiting the critical section. Thus, if one thread is executing the critical section, any other threadthat attempts to enter the critical section will block until the original thread has exited the criticalsection.

Here is a simple demonstration of the threading.Lockmechanism. Two threads; one producesrandom integers into a shared list and the other consumes integers from the list.

1 #!/usr/bin/env python3234 import threading5 import random6 import time78 class SyncList:9

10 def __init__(self):11 self._theList =[]12 self._lock = threading.Lock()1314 def append(self,data):15 while len(self._theList) >= 4:16 print("list is full - %s going to sleep" % \17 threading.currentThread().getName())18 print ("full list is" , self._theList)19 time.sleep(1)20 self._lock.acquire()21 try:22 self._theList.append(data)23 finally:24 self._lock.release()2526 def remove(self):27 head = None28 while len(self._theList) == 0:29 print("list is empty; %s going to sleep" % \30 threading.currentThread().getName())31 time.sleep(1)32 self._lock.acquire()33 try:34 if len(self._theList) > 0:35 head = self._theList.pop(0)36 return head37 finally:38 self._lock.release()394041 class Producer(threading.Thread):4243 def __init__(self,sharedSyncList, tname):44 threading.Thread.__init__(self,name = tname)

78

Page 79:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

45 self._list = sharedSyncList4647 def produce(self):48 self._list.append(random.randrange(1,10))49 print (self.getName(), "list is now:", self._list)5051 def run(self):52 i = 053 while i <10:54 self.produce()55 time.sleep(2)565758 class Consumer(threading.Thread):5960 def __init__(self,sharedSyncList, tname):61 threading.Thread.__init__(self,name = tname)62 self._list = sharedSyncList6364 def consume(self):65 print(self.getName(), "just consumed", self._list.remove())6667 def run(self):68 while True:69 self.consume()7071 # main program7273 sharedList = SyncList()74 p = Producer(sharedList,"Producer")75 c = Consumer(sharedList,"Consumer")7677 p.start()78 c.start()

The locks implemented by threading.Lock do not have owners. That is, the release()method may be called by a thread that did not call acquire(). Consider the situation of a threadattempting to acquire() at the same lock twice.

1 >>> l.acquire()2 True3 >>> l.acquire() # gets stuck!

We see that the threading.Lock object does not care which thread is currently holding the lock;if the lock is held, any thread that attempts to acquire the lock will block, even if the same threadis already holding the lock.

Consider the following example in which a thread requires to retrieve two pieces of information fortwo distinct shared data sources. Access to the sources is protected by the same threading.Lockto prevent two distinct threads operating in these two sources concurrently. Nevertheless, accessingboth in one critical section is not allowed as there are additional threads that require only one ofthe sources. Possible retrieval functions for these two sources might look as follows.

1 lock = threading.Lock()23 def get_first_part():4 lock.acquire()5 try:6 #... fetch data for first part from shared object7 finally:

79

Page 80:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

8 lock.release()9 return data

1011 def get_second_part():12 lock.acquire()13 try:14 #... fetch data for second part from shared object15 finally:16 lock.release()17 return data

Here, we have a shared resource, and two access functions that fetch different parts from the resource.The access functions both use locking to make sure that no other thread can modify the resourcewhile we are accessing it.

Now, if we want to add a third function that fetches both parts, we quickly get into trouble. Thenaive approach is to simply call the two functions, and return the combined result:

1 def get_both_parts():2 first = get_first_part()3 second = get_second_part()4 return first, second

The problem here is that if some other thread modifies the resource between the two calls, we mayend up with inconsistent data. The obvious solution to this is to grab the lock in this function aswell:

1 def get_both_parts():2 lock.acquire()3 try:4 first = get_first_part()5 second = get_second_part()6 finally:7 lock.release()8 return first, second

However, this will not work; the individual access functions will get stuck, because the outer functionalready holds the lock. To work around this, you can add flags to the access functions that enablesthe outer function to disable locking, but this is error-prone, and can quickly get out of hand.Fortunately, the threading module contains a more practical lock implementation; re-entrant locks.

§13.4.2 Reentrant locks

The threading.RLock class is a version of simple locking that only blocks if the lock is heldby thread that is not the one currently holding the lock. If the current thread is trying to acquire alock that it?s already holding, execution continues as usual. This is different from what we saw forthreading.Lock.

1 lock = threading.Lock()2 lock.acquire()3 lock.acquire() # this will block45 lock = threading.RLock()6 lock.acquire()7 lock.acquire() # this won’t block

80

Page 81:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Re-entrant locks support ownership, so that release() could only be called by the thread thatcalled acquire() (and thus becoming the owner of the lock). Internally, it uses the concepts ofowning thread in addition to the locked/unlocked state used by primitive locks. In the locked state,some thread owns the lock; in the unlocked state, no thread owns it.

The main use for such locks is in enabling nested access to shared resources. To fix the accessfunctions get_first_part(), get_second_part(), and get_both_parts() above, we mayjust replace the simple lock with a re-entrant lock, and the nested calls will work.

1 lock = threading.RLock()23 def get_first_part():4 #... see above56 def get_second_part():7 #... see above89 def get_both_parts():

10 #... see above

Use the _is_owned()method of threading.RLock in order check whether the threading.RLockobject is currently owned.

Finally, let us note that threading.RLock is actually a factory function which returns aninstance of the most efficient version of the concrete RLock class that is supported by the platform.Consequently, the following will not work

class myRLock(threading.RLock):

§13.5 Condition variables and events

§13.5.1 Condition variables

We often desire to create more sophisticated threads that access a critical section only whensome conditions are met or some events occur. A condition variable represents a state change in theapplication, and a thread can wait for a given condition, or signal that the condition has happened.In Python such a notion is captured through the threading.Condition class (and also throughthreading.Event class which we shall meet later on).

Condition variables contain an underlying lock; in fact, condition variables are always associatedwith some kind of lock; this can be passed in when those are created or one will be created bydefault if none is passed. Passing a lock in (during construction) is useful when several conditionvariables must share the same lock. The lock is part of the condition object: no need to track thislock separately. The default underlying lock is a threading.RLock.

threading.Condition provides acquire() and release()methods. Calls to these meth-ods are delegated to the acquire() and release() methods of the underlying lock of the con-dition variable.

81

Page 82:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Additional methods threading.Condition (which must be called with the associated lockheld) are wait(), notify(), and notify_all(). When a thread has acquired the underlyinglock, calling method wait() releases the lock and causes the thread to block until it is awakenedby a call to notify() on the same threading.Condition object. Calling method notify()wakes up one thread, if any, waiting on the threading.Condition object. All waiting threadscan be singled by invoking the notify_all() method.

The full signature of notify() is notify(n=1). That is notify() wakes up at most nthreads, if any, waiting on the condition object.

Note that the notify() and notify_all() methods do not release the lock; this meansthat the thread or threads singled through these methods will not necessarily return from theirwait() call immediately, but only when the thread that called notify() or notify_all()finally relinquishes ownership of the lock.

A rudimentary producer/consumer scenario using condition variables looks roughly as follows.First we create a threading.Condition object that represents, say, that an element has beenadded into the shared resource; i.e., this is the state change that the consumer thread is waiting for.

1 # represents the addition of an item to a resource2 condition = threading.Condition()

The producer thread would look roughly like this.1 # producer thread2 #... generate item3 condition.acquire()4 #... add item to resource5 condition.notify() # signal that a new item is available6 condition.release()

The consumers must acquire the condition (and thus the related lock), and can then attempt tofetch items from the resource.

1 # consumer thread2 condition.acquire()3 item = None4 while True:5 #... get item from resource6 if item:7 break # if we have an item go to processing below8 else: # we could not get an item (perhaps the shared data is empty)9 condition.wait() # sleep until item becomes available

10 condition.release()11 # start processing the item

Let us consider a more elaborate example. In a producer/consumer scenario, assume we have abounded buffer to fill and we wish for the producer and consumer threads to take turns as follows:when the buffer is empty the producer is allowed to resume its work and fill the buffer up; only whenthe buffer is full the consumer is allowed to resume its work and empty the buffer.

In what follows we alter the code above for the producer/consumer program to fit for the scenariodescribed above using condition variables.

1 #!/usr/bin/env python3

82

Page 83:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

234 import threading5 import random67 class SyncBoundedList:89 def __init__(self, limit= 5):

10 self._theList =[]11 self._sizeLimit = limit12 self._cond = threading.Condition()1314 def fill(self):15 self._cond.acquire()1617 try:18 while len(self._theList) > 0:1920 print ("%s waiting for buffer to become empty" % \21 threading.currentThread().getName())2223 self._cond.wait()2425 print ("%s is filling the buffer" % \26 threading.currentThread().getName())2728 while len(self._theList) <= self._sizeLimit:29 self._theList.append(random.randrange(1,10))3031 print (threading.currentThread().getName(), \32 ": buffer is now:", self._theList)33343536 finally:37 self._cond.notify()38 self._cond.release()3940 def consume(self):41 self._cond.acquire()4243 try:44 while len(self._theList) < self._sizeLimit:4546 print("%s is waiting for buffer to become full" % \47 threading.currentThread().getName())4849 self._cond.wait()5051 print ("%s is now reading the buffer as follows:" % \52 threading.currentThread().getName())5354 while len(self._theList) > 0 :55 print (threading.currentThread().getName(), \56 "just read", self._theList.pop(0))5758 print ("buffer is now empty", self._theList)596061 finally:62 self._cond.notify()63 self._cond.release()646566 class Producer(threading.Thread):67

83

Page 84:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

68 def __init__(self,sharedSyncList, tname):69 threading.Thread.__init__(self,name = tname)70 self._list = sharedSyncList7172 def run(self):73 while True:74 self._list.fill()75767778 class Consumer(threading.Thread):7980 def __init__(self,sharedSyncList, tname):81 threading.Thread.__init__(self,name = tname)82 self._list = sharedSyncList8384 def run(self):85 while True:86 self._list.consume()8788 # main program8990 sharedList = SyncBoundedList()91 p = Producer(sharedList,"Producer")92 c = Consumer(sharedList,"Consumer")9394 p.start()95 c.start()

§13.5.2 Events

A close relative of threading.Condition is threading.Event. The latter captures thescenario where one thread signals that an event has occurred and notifies all threads waiting for thisevent.

Objects of threading.Event maintain an internal flag that is initially set to False (i.e., theevent has not occurred). A thread that calls Event method wait() blocks until the event occurs.Use the method set() to set the flag to true and awaken all waiting threads. A thread that callswait() after the flag is True does not block at all. Method isSet() returns true if the flag isTrue. Use method clear() to set the flag to False.

1 event = threading.Event()23 # a client thread can wait for the flag to be set4 event.wait()56 # a server thread can set or reset it7 event.set()8 event.clear()

Let us rewrite the above consumer/producer program using threading.Event instead ofthreading.Condition.

1 #!/usr/bin/env python3234 import threading

84

Page 85:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

5 import random67 class SyncBoundedList:89 def __init__(self, limit= 5):

10 self._theList =[]11 self._sizeLimit = limit12 self._canRead = threading.Event()13 self._canWrite = threading.Event()14 self._canWrite.set()1516 def fill(self):1718 self._canWrite.wait()1920 print ("%s is filling the buffer" % \21 threading.currentThread().getName())2223 while len(self._theList) <= self._sizeLimit:24 self._theList.append(random.randrange(1,10))2526 print (threading.currentThread().getName(), \27 ": buffer is now:", self._theList)2829 self._canWrite.clear()3031 self._canRead.set()3233 def consume(self):34 self._canRead.wait()3536 print ("%s is now reading the buffer as follows:" % \37 threading.currentThread().getName())3839 while len(self._theList) > 0 :40 print (threading.currentThread().getName(), \41 "just read", self._theList.pop(0))4243 print ("buffer is now empty", self._theList)4445 self._canRead.clear()4647 self._canWrite.set()4849 class Producer(threading.Thread):5051 def __init__(self,sharedSyncList, tname):52 threading.Thread.__init__(self,name = tname)53 self._list = sharedSyncList5455 def run(self):56 while True:57 self._list.fill()58596061 class Consumer(threading.Thread):6263 def __init__(self,sharedSyncList, tname):64 threading.Thread.__init__(self,name = tname)65 self._list = sharedSyncList6667 def run(self):68 while True:69 self._list.consume()70

85

Page 86:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

71 # main program7273 sharedList = SyncBoundedList()74 p = Producer(sharedList,"Producer")75 c = Consumer(sharedList,"Consumer")7677 p.start()78 c.start()

§13.6 Some subtleties

The wait() and notify() methods of threading.Condition, for instance, must be calledby the thread who owns the underlying lock. The traditional paradigm here is the following.

1 c = threading.Condition()23 c.acquire()45 try:6 while <some condition is not met>:7 c.wait()89 # do something as the condition is now met

101112 finally:13 c.notify()14 c.release()

The thread calling wait() releases the lock it owns and wait()s to reacquire() it. If the callingthread to wait() has not acquired the lock at the time of the call, a RuntimeError is raised.The same is true when invoking notify() and notify_all().

Why is it that only lock owners are allowed to invoke wait() and notify()? What are thepotential problems that may occur if we were not to enforce this rule? The short answer is thatwait()ing threads can miss notify() signals that awake them leading to deadlocks and starvationof threads.

Let us consider a concrete example written here not in Python. In this example two threads aconsumer and producer share a (blocking) queue in order to produce and consume elements. Theproducer creates elements and enqueue()s them while the consumer dequeue()s them from theshared queue. Assume now that there are two methods wait() and notify() that can be calledin a manner unattached to any lock.

1 #non-Python code23 class BlockingQueue:45 def __init__(self,...):6 #initialise7 self.buffer = []89 def enqueue(self, e):

10 self.buffer.append(e)11 notify() # inform some consumer that the buffer is non-empty12

86

Page 87:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

13 def dequeue(self):14 while len(self.buffer) == 0:15 wait() # wait until there is something in the buffer16 return self.buffer.pop(0)

Here is a likely scenario.

1. A consumer thread calls dequeue() and sees that the buffer is empty.

2. Before the consumer thread goes calls wait(), a producer thread gets CPU attention and per-forms a full execution of enqueue(), that is, it performs both self.buffer.append(e)and call notify()

3. The consumer thread resumes execution; it now calls wait() and thus misses the notify()just invoked.

4. Producer thread finishes its work.

5. Consumer thread never wakes up.

There can be more devastating scenarios can occur when the two threads notify each other; in whichcase one of them entering an indefinite wait will cause a deadlock.

We require a guarantee that the waiter and the notifier agree about the state of the predicate.The waiter checks the state of the predicate at some point slightly before it goes to sleep, but itdepends for correctness on the predicate being true when it goes to sleep. The predicate that theproducer and consumer need to agree upon above is that when the buffer is empty.

Suppose now that we have two shared resources amongst two threads. To carry out their workeach of the threads requires access to both resources. The code below shows an incorrect way to useRLocks in order to synchronise access of the threads to the resources. Indeed, this code may resultin a deadlock as demonstrated in the output.

1 #!/usr/bin/env python323 import threading4 import time567 class Reader (threading.Thread):89 def __init__(self,res1,res2,lock1,lock2,name):

10 threading.Thread.__init__(self, name = name )11 self.res1 = res112 self.res2 = res213 self.lock1 = lock114 self.lock2 = lock21516 def run(self):1718 while True:19 self.lock1.acquire()20 print(self.name, "got lock1")21 self.lock2.acquire()22 print(self.name, "got lock2")23 if len(self.res1) == 0 or len(self.res2) == 0:

87

Page 88:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

24 self.lock2.release()25 print(self.name, "released lock2")26 self.lock1.release()27 print(self.name, "released lock1")28 break29 print(self.name, "popped from 1 the number:", self.res1.pop(0))30 print(self.name, "popped from 2 the number:", self.res2.pop(0))31 self.lock2.release()32 print(self.name, "released lock2")33 self.lock1.release()34 print(self.name, "released lock1")35 time.sleep(0.01)3637 #main program38 res1 = [1,2,3,4,5,6]39 res2 = [7,8,9,10,11,12]4041 rlock1 = threading.RLock()42 rlock2 = threading.RLock()4344 reader1 = Reader(res1,res2,rlock1,rlock2,"thread 1")45 reader2 = Reader(res1,res2,rlock2,rlock1,"thread 2")4647 reader1.start()48 reader2.start()49 ------------------------------------------50 Output:5152 thread 1 got lock153 thread 1 got lock254 thread 1 popped from 1 the number: 155 thread 1 popped from 2 the number: 756 thread 1 released lock257 thread 1 released lock158 thread 2 got lock159 thread 2 got lock260 thread 2 popped from 1 the number: 261 thread 2 popped from 2 the number: 862 thread 2 released lock263 thread 2 released lock164 thread 1 got lock165 thread 2 got lock1

The deadlock can be observed in the last two lines of the output.The problem here is that the RLocks of this code were used in an interleaving manner where

the correct way is to use them in a nested order. We explain. To interleave the locks means thefollowing.

1 #in the run method of thread 12 def run(self):3 self.lock1.acquire()4 self.lock2.acquire()5 #do the work6 self.lock1.release()7 self.lock2.release()89 #in the run method of thread 2

10 def run(self):11 self.lock2.acquire()12 self.lock1.acquire()13 #do the work14 self.lock2.release()15 self.lock1.release()

88

Page 89:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

What happened in the code above is that thread 1 acquire()d lock1 but before it was able toacquire() lock2 thread 2 was given the CPU attention which acquire()d lock2. At thispoint both threads are attempting to acquire a lock that is owned by the other. The solution is tomake sure that all threads accessing these two resources acquire() the locks in the same order.For instance, as follows.

1 #in the run method of thread 12 def run(self):3 self.lock1.acquire()4 self.lock2.acquire()5 #do the work6 self.lock1.release()7 self.lock2.release()89 #in the run method of thread 2

10 def run(self):11 self.lock1.acquire()12 self.lock2.acquire()13 #do the work14 self.lock2.release()15 self.lock1.release()

Throughout we have seen the following paradigm.1 condition.acquire()2 while <some condition is not met>:3 condition.wait()

It is recommended that a thread check the conditions it is waiting to be met after waking up as awake up maybe unrelated to any notify() signal. For instance, the thread scheduler might bea least frequency scheduler which at some point notices that the waiting thread has not receivedattention in a while causing its scheduling frequency to drop and consequently decides to wake upthe thread.

In the above code we made sure to call notify() in the finally clause of the try. Why notin the try block? If an exception is raised prior to the call of notify() then threads that wait()on the associated condition will starve. Here is a simple example of that.

1 #!/usr/bin/env python323 import threading4 import time56 class A(threading.Thread):78 def __init__(self, cond,t):9 threading.Thread.__init__(self)

10 self.cond = cond11 self.t = t1213 def run(self):1415 self.cond.acquire()16 while self.t[0] != 1:17 self.cond.wait()18 self.cond.release()1920 class B(threading.Thread):21

89

Page 90:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

22 def __init__(self, cond,t):23 threading.Thread.__init__(self)24 self.cond = cond25 self.t = t2627 def run(self):2829 self.cond.acquire()30 try:31 while self.t[0] != 0:32 self.cond.wait()33 self.t[0] = 134 raise Exception("stop")35 self.cond.notify()3637 finally:3839 self.cond.release()4041 #main4243 t= [0]44 cond = threading.Condition()45 a = A(cond,t)46 b = B(cond,t)47 a.start()48 b.start()

In this code the thread a will starve.

§13.7 Semaphores

This is one of the oldest synchronisation primitives in the history of computer science, inventedby the early Dutch computer scientist Edsger W. Dijkstra (he used the names P() and V() insteadof acquire() and release()).

A semaphore manages an internal counter which is decremented by each acquire() call andincremented by each release() call. The counter can never go below zero; when acquire() findsthat it is zero, it blocks, waiting until some other thread calls release(). A call to release()may wake up at most one thread that is blocked on the counter.

In Python semaphores are created with class threading.Semaphore. The initial value ofthe internal counter can be passed as an argument to the Semaphore constructor (default is 1).Because the internal counter can never have a negative value, specifying a negative counter valueraises an AssertionError exception.

The counter of threading.Semaphore is unbounded, i.e., arbitrarily many release()s canbe performed on it. A more practical semaphore implementation is that of a bounded semaphorein which the counter is bounded and calls to release() check whether the current value of thecounter does not exceed its initial value. If it does, ValueError is raised. Such semaphores carecaptured through threading.BoundedSemaphore; the bound on the counter of the semaphorecan be passed to the constructor of this class. The default value is one; i.e., by default all instancesof threading.BoundedSemaphore are mutexes. The initial value of the BoundedSemaphoreis the value passed to it during the call to its constructor.

90

Page 91:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Unlike say re-entrant lock, the calls to acquire() and release() of threading.Semaphoreand threading.BoundedSemaphore can be made by any thread. There is no notion of owner-ship. These however are different from threading.Lock as they support multiple release()sand acquire()s without blocking or raising an exception depending on the limit of the counter.

Of special interest is the so called binary semaphore whose counter is bounded by one. Acommon name for such a semaphore is mutex as such a semaphore is essentially a lock that providesmutual exclusion over some critical region. In particular, a binary semaphore behaves similarly tothreading.Lock. A mutex is then created as follows.

1 import threading23 mutex = threading.BoundedSemaphore(1)

It is used as follows.1 mutex.acquire()2 try:3 # access shared data or enter critical section4 finally:5 mutex.release()

Let us now consider an application which will set semaphores apart from locks. Sometimes we arewilling to have more than one thread inside a critical section; with semaphores this can be achieved.For instance, consider the problem of having a shared database amongst a single writer thread andseveral reader threads. Several reader threads reading concurrently from the shared database isallowed, but a reader thread and the writer thread accessing the shared database at the same timeis not permitted. The following code has a subtle flaw which may lead to inefficiency. See if you canpinpoint it.

1 #!/usr/bin/env python3234 import threading5 import random6 import time789 class SyncData:

1011 READERS_LIMIT = None1213 def __init__(self, readersLimit= 5):14 self.data =random.randrange(1,10)15 SyncData.READERS_LIMIT = readersLimit16 self.currentReaders = 017 self.dbSem = threading.BoundedSemaphore(1) # mutex for DB18 self.mutex = threading.RLock() #mutex for self.currentReaders192021 def read(self):22 data = None23 curThread = threading.currentThread().getName()2425 self.mutex.acquire()26 try:27 if self.currentReaders >= SyncData.READERS_LIMIT:28 print("%s: too many readers; I pass" % curThread)

91

Page 92:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

29 return data30 finally:31 self.mutex.release()3233 self.mutex.acquire()34 try:35 if self.currentReaders == 0:36 print("%s is the sole reader in DB; get the semaphore" % curThread)37 self.dbSem.acquire()38 finally:39 self.mutex.release()4041 self.mutex.acquire()42 try:43 self.currentReaders += 144 finally:45 self.mutex.release()4647 data = self.data # read48 print(curThread, "just read:", data)4950 self.mutex.acquire()51 try:52 self.currentReaders -= 153 finally:54 self.mutex.release()5556 self.mutex.acquire()57 try:58 if (self.currentReaders == 0):59 print ("%s last reader; release semaphore" % curThread)60 self.dbSem.release()61 finally:62 self.mutex.release()6364 return data6566 def write(self, data):67 curThread = threading.currentThread().getName()68 self.dbSem.acquire()69 try:70 print (curThread, "is writing", data)71 self.data = data72 finally:73 self.dbSem.release()747576 class Writer(threading.Thread):7778 def __init__(self,sharedDB, tname):79 threading.Thread.__init__(self,name = tname)80 self.DB = sharedDB8182 def run(self):83 while True:84 self.DB.write(random.randrange(1,10))85 time.sleep(1)86878889 class Reader(threading.Thread):9091 def __init__(self,sharedDB, tname):92 threading.Thread.__init__(self,name = tname)93 self.DB = sharedDB94

92

Page 93:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

95 def run(self):96 while True:97 self.DB.read()98 time.sleep(1)99

100 # main program101102 theDB = SyncData(2) # i.e. we allow 3 readers at once103 writer = Writer(theDB,"Writer")104 theReaders = []105106 for i in range(3):107 theReaders .append(Reader(theDB,"Reader"+str(i)))108109 writer.start()110111 for reader in theReaders:112 reader.start()

The problematic part of this code is the following section taken from the read() method.1 self.mutex.acquire()2 try:3 if self.currentReaders == 0:4 print("%s is the sole reader in DB; get the semaphore" % curThread)5 self.dbSem.acquire()6 finally:7 self.mutex.release()89 self.mutex.acquire()

10 try:11 self.currentReaders += 112 finally:13 self.mutex.release()

Here we separate the acquire() of the semaphore from the incrementation of currentReaders.This is problematic as demonstrated by the following scenario. Suppose reader A is the first reader,it enters to the first critical section and acquire()s the semaphore. As it moves between the twocritical sections it is interrupted by another reader, say B, that enters the first critical section seesthat currentReaders is still equal to zero (as reader A) still did not have the chance to incrementit. Consequently, reader B tries to acquire() the semaphore but is blocked as the coin has alreadybeen taken by reader A.

To resolve this we have to put the incrementation and the acquire() of the semaphore in thesame critical section.

§13.8 The dining philosophers

A classical problem in thread synchronisation is the so called dining philosophers problem. Con-sider five people, philosophers in our case, seating to eat dinner at a round table. Each has a platein front of her. In order to eat, a philosopher requires two forks one for each hand. However, thenumber of available forks is five, and those are placed between the plates, as depicted in Figure ??.An additional limitation is that a philosopher can only reach forks that are adjacent to her.

Imagine now that each philosopher is represented by a thread; we are to write a program inwhich each philosopher gets to complete her dish without wounding up in a deadlock.

93

Page 94:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Figure 5: The dining philosophers problem.

It is easy to see that a strategy that may lead to a deadlock is if a philosopher tries first toacquire fork to her left and then in a separate effort the fork to her right. Something in the form:

12 class Philosopher (threading.Thread):34 def run(self):56 self.getLeftFork()7 self.getRightFork()

We shall employ a different strategy; we shall try to grab both forks at once and will choose topass if we see that this cannot be attained. At the heart of our solution we have two lists (or arrays)of length five (equal to the number of philosophers). The first list/array is called state wherestate[i] is the state of the ith philosopher which can be hungry, eating, or thinking. The secondlist/array is called forks of binary semaphores. forks[i] will indicate whether the two forkssurrounding the ith philosopher are free for the taking; we shall use the 0 value of the semaphore toindicate that these are not free and 1 otherwise.

In the code below we assume that each philosopher wants to eat 3 times.1 #!/usr/bin/env python323 import threading45 class Philosopher (threading.Thread):

94

Page 95:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

67 #shared class attributes89 state = ["HUNGRY" for i in range(5)]

1011 forks = [threading.BoundedSemaphore(1) for i in range(5)]1213 state_mutex = threading.BoundedSemaphore(1)141516 #static utility methods1718 def left(index):19 return (index -1) % 52021 def right(index):22 return (index + 1) % 5232425 def __init__(self, index):26 threading.Thread.__init__(self)27 self.index = index2829 def run(self):30 cur = 13132 while cur <= 3:33 self.think(cur)34 self.take_forks()35 self.eat(cur)36 cur+=137 self.put_forks()3839 def think(self,cur):40 print("Philosopher "+str(self.index)+" is thinking for the "+ str(cur)+" time")4142 def eat(self,cur):43 print("Philosopher "+str(self.index)+" is eating for the "+ str(cur)+" time")4445 def take_forks(self):46 Philosopher.state_mutex.acquire() # enter critical section47 try:48 Philosopher.state[self.index] = "HUNGRY" # mark that you are hungry49 Philosopher.test_forks_availability(self.index) # see if the forks are free50 finally:51 Philosopher.state_mutex.release() #exit critical section5253 Philosopher.forks[self.index].acquire() #block if forks are not acquired5455 def put_forks(self):56 Philosopher.state_mutex.acquire()57 try:58 Philosopher.state[self.index] = "THINKING"59 Philosopher.test_forks_availability(Philosopher.left(self.index))60 Philosopher.test_forks_availability(Philosopher.right(self.index))61 finally:62 Philosopher.state_mutex.release()6364 #static methods6566 def test_forks_availability(index):67 LEFT = Philosopher.left(index)68 RIGHT = Philosopher.right(index)69 if Philosopher.state[index] == "HUNGRY" and \70 Philosopher.state[LEFT] != "EATING" and \71 Philosopher.state[RIGHT] != "EATING" :

95

Page 96:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

7273 Philosopher.state[index] = "EATING"7475 if Philosopher.forks[index]._value == 0:76 Philosopher.forks[index].release()777879 # main program8081 for philosopher in [Philosopher(i) for i in range(5)]:82 philosopher.start()83 -----------------------------------------------84 Output:8586 Philosopher 0 is thinking for the 1 time87 Philosopher 0 is eating for the 1 time88 Philosopher 0 is thinking for the 2 time89 Philosopher 1 is thinking for the 1 time90 Philosopher 1 is eating for the 1 time91 Philosopher 2 is thinking for the 1 time92 Philosopher 1 is thinking for the 2 time93 Philosopher 3 is thinking for the 1 time94 Philosopher 2 is eating for the 1 time95 Philosopher 4 is thinking for the 1 time96 Philosopher 2 is thinking for the 2 time97 Philosopher 3 is eating for the 1 time98 Philosopher 1 is eating for the 2 time99 Philosopher 4 is eating for the 1 time

100 Philosopher 1 is thinking for the 3 time101 Philosopher 4 is thinking for the 2 time102 Philosopher 2 is eating for the 2 time103 Philosopher 0 is eating for the 2 time104 Philosopher 2 is thinking for the 3 time105 Philosopher 0 is thinking for the 3 time106 Philosopher 4 is eating for the 2 time107 Philosopher 3 is thinking for the 2 time108 Philosopher 4 is thinking for the 3 time109 Philosopher 3 is eating for the 2 time110 Philosopher 1 is eating for the 3 time111 Philosopher 3 is thinking for the 3 time112 Philosopher 4 is eating for the 3 time113 Philosopher 2 is eating for the 3 time114 Philosopher 0 is eating for the 3 time115 Philosopher 3 is eating for the 3 time

§13.9 Thread barriers

Class threading.Barrier provides a simple synchronisation primitive for use by a fixednumber of threads that need to wait for each other. Each of the threads tries to pass the barrierby calling the wait() method of the barrier and will block until all of the threads have made thecall. At this point, the threads are released simultaneously. The barrier can be reused any numberof times for the same number of threads.

Here is a simple way to synchronise a client and server thread.1 b = Barrier(2, timeout=5)23 #assume this in the server thread4 def server():5 start_server()

96

Page 97:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

6 b.wait() # wait for the client thread7 while True:8 connection = accept_connection()9 process_server_connection(connection)

1011 #assume this in the client thread12 def client():13 b.wait() # wait for the server thread14 while True:15 connection = make_connection()16 process_client_connection(connection)

Let us review the constructor and some of the methods of this class more rigorously. First,the constructor threading.Barrier(parties, action=None, timeout=None) creates abarrier object for parties number of threads. An action, when provided, is a callable to becalled by one of the threads when they are released. timeout is the default timeout value if noneis specified for the wait() method.

We call the wait(timeout=None) in order to try and pass the barrier. When all the threadsparty to the barrier have called this function, they are all released simultaneously. If a timeout isprovided, it is used in preference to any that was supplied to the class constructor.

The return value is an integer in the range 0 to parties ? 1, different for each thread. Thiscan be used to select a thread to do some special housekeeping, for instance:

1 i = barrier.wait()2 if i == 0:3 # Only one thread needs to print this4 print("passed the barrier")

If an action was provided to the constructor, one of the threads will have called it prior tobeing released. Should this call raise an error, the barrier is put into the broken state.

If the call to wait() times out, the barrier is put into the broken state. Indeed, the wait()method may raise a BrokenBarrierError exception if the barrier is broken or reset() (seebelow) while a thread is waiting.

The reset() method returns the barrier to the default, empty state. Any threads waiting onit will receive the BrokenBarrierError exception. Note that using this function may requiresome external synchronisation if there are other threads whose state is unknown. If a barrier isbroken it may be better to just leave it and create a new one.

The abort() method puts the barrier into a broken state. This causes any active or futurecalls to wait() to fail with the BrokenBarrierError. Use this for example if one of the threadsneeds to abort, to avoid deadlocking the application. It may be preferable to simply create thebarrier with a sensible timeout value to automatically guard against one of the threads going awry.

Lastly, threading.Barrier objects have the following attributes: parties is the numberof threads required to pass the barrier. n_waiting is the number of threads currently waiting inthe barrier; and broken is a boolean that is True if the barrier is in the broken state.

§13.10 Custom thread pool (that does not work)

97

Page 98:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Below is our proposal for a rudimentary thread pool. The code involves three entities. ATaskFeeder class, a PoolThread class and a PoolManager class. Each of these is a thread.Roughly speaking, the code below creates a single instance of TaskFeeder that "feeds" task intoa single instance of PoolManager. The latter, manages three PoolThreads and delegates thetasks to these threads.

One key feature of this code is that all thread synchronisation is done internally within the variousthreads; this is unlike previous examples where we passed common locks and events to threads thatneeded to synchronise. Here, these mechanisms are invoked through class methods.

The code proposed which we will study in class does not work as you can see from the outputs.Provided below.

1 #!/usr/bin/env python3234 import threading5 import copy67 class PoolThread(threading.Thread):89

10 def __init__(self,manager,name):1112 threading.Thread.__init__(self)131415 self.task = None16 self.alive = True17 self.name = name18 self.manager = manager1920 self.task_mutex = threading.RLock()21 self.new_task_ready = threading.Event()2223 # only called by manager24 def setTask(self, newTask):25 self.task_mutex.acquire()26 try:27 print ("Pool thread ", self.name, " has a new task: ", newTask())28 self.task = newTask29 finally:30 self.new_task_ready.set()31 self.task_mutex.release()3233 #def kill(self):34 # self.alive = False3536 #called by pool thread37 def performTask(self):38 self.task_mutex.acquire()39 try:40 print("Pool Thread ", self.name, "is performing ", self.task())41 finally:42 self.task_mutex.release()4344 def run(self):4546 self.manager.ready_for_new_task(self)4748 while self.alive:4950 self.new_task_ready.wait()

98

Page 99:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

51 self.performTask()52 self.new_task_ready.clear()53 self.manager.ready_for_new_task(self)54 ###################################################################555657 class TaskFeeder(threading.Thread):5859 def __init__(self, manager):60 threading.Thread.__init__(self)61 self.manager = manager6263 def run(self):6465 for i in range(20):6667 print("feeder loading task ", i)68 self.manager.append_task(lambda : "task " + str(i))69 self.manager.wait()7071 #self.manager.kill()7273 ############################################################74 class PoolManager(threading.Thread):7576 def __init__(self):7778 threading.Thread.__init__(self)7980 self.feeder_has_to_wait = threading.Event()81 self.task_cond = threading.Condition()82 self.ready_thread_cond = threading.Condition()8384 self.tasks = []85 self.ready_pool_threads = []86878889 # only invoked by feeder90 def append_task(self,f):9192 self.task_cond.acquire()93 try:94 self.tasks.append(f)95 finally:96 self.task_cond.notify()97 self.task_cond.release()9899 # only invoked by feeder

100 def wait(self):101 self.feeder_has_to_wait.wait()102 self.feeder_has_to_wait.clear()103104 # only invoked by pool threads105 def ready_for_new_task(self,poolThread):106 self.ready_thread_cond.acquire()107 try:108 self.ready_pool_threads.append(poolThread)109 finally:110 self.ready_thread_cond.notify()111 self.ready_thread_cond.release()112113 def run(self):114115 for thread in [PoolThread(self, str(i)) for i in range(3)]:116 thread.start()

99

Page 100:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

117118 while True:119120 theTask = None121 theThread = None122 self.task_cond.acquire()123 try:124125 while len(self.tasks) == 0:126127 self.task_cond.wait()128129130 theTask = self.tasks.pop(0)131 print("Manager fetching ", theTask())132133 finally:134 self.task_cond.release()135136 self.feeder_has_to_wait.set()137138 self.ready_thread_cond.acquire()139 try:140141142 while len(self.ready_pool_threads) == 0:143144 self.ready_thread_cond.wait()145146 theThread = self.ready_pool_threads.pop(0)147 print("Manager fetched thread ", theThread.name)148149 finally:150151 self.ready_thread_cond.release()152153154 print("Manager assigns ", theTask(), "to thread ", theThread.name)155 theThread.setTask(theTask)156 print("Manager set task ", theTask(), " to thread ", theThread.name)157158159 ####################160 #main161162 manager = PoolManager()163 feeder = TaskFeeder(manager)164165 manager.start()166 feeder.start()

The first partial output from which we can learn that there is a problem is the following. Weadd question marks where there are problems.

1 feeder loading task 02 Manager fetching task 03 Manager fetched thread 04 Manager assigns task 0 to thread 05 Pool thread 0 has a new task: task 06 Manager set task task 0 to thread 07 feeder loading task 18 Pool Thread 0 is performing task 1 ? what happened to task 0?9 Manager fetching task 1

10 Manager fetched thread 111 Manager assigns task 1 to thread 1

100

Page 101:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

12 Pool thread 1 has a new task: task 113 Manager set task task 1 to thread 114 Pool Thread 1 is performing task 1 ? task 1 already done by thread 0 ?15 feeder loading task 216 ............................17 ............................

So here we see that for some reason task 0 was dropped despite it was assigned to thread 0. Instead,thread 0 performs task 1; which is later performed by thread 1 again. The cause for both theseproblems is the same.

Consider the object theTask defined in the run() method of the PoolManager. By settingtheTask = self.tasks.pop(0) we point this reference to the element at index 0 in the Carray implementing the tasks list. In the above code we see that after the manager invokes thesetTask() method of thread 0 the feeder thread got attention at which point it loaded task 1using append to the tasks list. By doing so it set the element at index 0 of the task list to theobject representing task 1. The object theTask being a pointer to the element at index 0 of thislist changes as well prior to thread 0 getting attention. When thread 0 gets attention on the nextline its task has been changed to task 1. Next, we see that the feeder does not get attention untiltask 1 is properly assigned by he manager object. Consequently, thread 1 performs task 1 as well.

The main lesson here is that theTask is a pointer to shared data and consequently becomesshared data as well. Indeed, within the methods of PoolThread access to theTask is protectedby an RLock. However, this RLock is unknown to the feeder. Consequently, we have now identifieda race condition over theTask by the instances of PoolThread and the feeder thread.

A naive approach at an attempt to fix the situation is to have theTask point to a copy ofself.tasks[0]. We recall that taking a slice of a list creates a copy of list. Perhaps the followingwill be sufficient.

1 .............................2 .............................3 def run(self):45 for thread in [PoolThread(self, str(i)) for i in range(3)]:6 thread.start()78 while True:9

10 theTask = None11 theThread = None12 self.task_cond.acquire()13 try:1415 while len(self.tasks) == 0:1617 self.task_cond.wait()181920 theTask = self.tasks[0:1][0]21 self.tasks.pop(0) # to remove the task22 print("Manager fetching ", theTask())2324 finally:25 self.task_cond.release()2627 self.feeder_has_to_wait.set()

101

Page 102:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

28 ..................................29 ..................................

This would not work either due to the following.1 >>> lst = [1,2,3]2 >>> lst[:] is lst3 False4 >>> lst[:][0] is lst[0]5 True

In Exercise 14.29 below you are asked to fix this code.

§13.11 The WITH statement

All of the objects provided by the threading module that have acquire() and release()methods can be used as context managers for a with statement; that is, Lock, RLock, Condition,Semaphore, and BoundedSemaphore can all be used with the with statement. The acquire()method will be called when the block is entered, and release() will be called when the block isexited. That is the statement

1 with some_lock:2 # do something...

is equivalent to1 some_lock.acquire()2 try:3 # do something...4 finally:5 some_lock.release()

The use of this statement in this course is forbidden!

§14 Exercises with solutions

Exercise 14.1. What would be printed here?1 #!/usr/bin/env python323 x=1; y=2; z =345 x,y,z = z,x,y67 print(x,y,z)

Exercise 14.2. Let A,B, and C be three lists of integers.

(a) Generate the set{(a, b, c) : a ∈ A, b ∈ B, c ∈ C and c < a < b}.

102

Page 103:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

(b) Produce the set in reverse lexicographic order.

1 #!/usr/bin/env python323 A = list(range(1,5))4 B = list(range(2,6))5 C = list(range(-2,2))67 list = [[a,b,c] for a in A for b in B for c in C if c <= a < b ]89 list.sort()

10 list.reverse()11 print(list)

Listing 1: Proposed solution for Exercise 14.2

Exercise 14.3. Given a set A of positive integers generate all 3-tuples of members of A that donot form a 3-term arithmetic progressions in A.

A 3-term arithmetic progression is a triple of numbers of the form {x, x+ d, x+ 2d}.

1 #!/usr/bin/env python323 A = list(range(1,11))4 x5 print([(x,y,z) for x in A for y in A for z in A if x + z !=2*y])

Listing 2: Proposed solution for Exercise 14.3

Exercise 14.4. Flatten the list vec = [[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]using a one line list comprehension.

1 #!/usr/bin/env python32 vec = [[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]3 print([n for e1 in vec for e2 in e1 for n in e2])

Listing 3: Proposed solution for Exercise 14.4

Exercise 14.5. Two matrices in the form of lists are given; for instance:1 mat1 = [2 [1,2,6,11],3 [3,4,9,20]4 ]56 mat2 = [7 [3,7,0],8 [14,18,19],9 [7,8,9],

10 [9,9,9]11 ]

That is, the elements of the lists are the rows of the matrices. Write a (single line) list comprehensionto calculate the product of two matrices given in this form. For instance, for the matrices above theanswer should be:

103

Page 104:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 [[172, 190, 191], [308, 345, 337]]

In your solution it is not allowed to define any custom functions

To produce a solution for this exercise let us first write a solution that defines custom functionsand in a second iteration these will be replaced in order to attain the requested list comprehension.

1 #!/usr/bin/env python323 #calculate inner product4 def inner(l1,l2):5 product = 06 for i,j in zip(l1,l2):7 product += i*j8 return product9

10 #transpose a matrix11 def transpose(matrix):12 ROW_LEN = len(matrix[0])13 return [[row[i] for row in matrix] for i in range(ROW_LEN)]1415 mat3 = [[inner(row,column) for column in transpose(mat2)] for row in mat1]

In mat3 we see a concise description of the list comprehension we are after. Next, we replace theinvocation of transpose() in the definition of mat3.

1 mat4 = [[inner(row,column) for column in [[row[i] for row in mat2] \2 for i in range(len(mat2[0]))] ] for row in mat1]

Prior to replacing inner() let us see how to calculate the inner product of two lists (vectors),namely l1 and l2, in a list comprehension.

1 sum([i*j for i,j in zip(l1,l2)])

Now we replace inner() as follows.1 mat5 = [[sum([i*j for i,j in zip(row,column)]) \2 for column in [[row[i] for row in mat2] \3 for i in range(len(mat2[0]))] ] for row in mat1]

Exercise 14.6. Implement the mergesort algorithm for lists.

1 def mergeSort( theList ):2 if len(theList) <= 1 :3 return theList4 else :5 mid = len(theList) // 26 leftHalf = mergeSort( theList[ :mid ] )7 rightHalf = mergeSort( theList[ mid: ] )8 newList = mergeOrderedLists( leftHalf, rightHalf )9 return newList

1011 def mergeOrderedLists(list1, list2):12 newList = []13 i1 = i2 = 014 while i1 < len(list1) and i2 < len(list2):15 while i1 < len(list1) and list1[i1] <= list2[i2] :16 newList.append(list1[i1])

104

Page 105:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

17 i1+=118 if i1 >= len(list1):19 break20 while i2 < len(list2) and list1[i1]> list2[i2] :21 newList.append(list2[i2])22 i2+=123 if i2 >= len(list2):24 break25 while i1 < len(list1):26 newList.append(list1[i1])27 i1+=128 while i2 < len(list2):29 newList.append(list2[i2])30 i2+=131 return newList

Listing 4: Proposed solution for Exercise 14.6

Exercise 14.7. The following class Node is used in order to implement a singly linked listimplementation.

1 class Node:2 def __init__(self,data = None):3 self.data = data4 self.next = None

That is, an instance of Node has a next field (which is a reference to the next Node in the list).In the implementation of the list the last node has its next field set to None.

Two references, namely list1 and list2, for singly linked lists as described above are given.It is known that the two lists coincide at some node. That is, list1 has a unique Node instance,say, node1 and list2 has a unique Node instance, say, node2 satisfying

node1.next == node2.next.

For instance, it could be that

node1.next == node2.next == None.

Design an algorithm linear in the (combined) lengths of the two lists that finds the point in whichlist1 and list2 coincide; put another way, find the instance of Node referenced by node1.nextand node2.next; handle the case that the point of intersection is at None.

Exercise 14.8. A common data structure used for Least Recently Used scheduling systems arehash maps coupled with the capability of iterating over the entries (i.e., (key,value) pairs) of the mapin the order those were inserted into the map. For instance, if two mappings are inserted into themap the second mapping is most recently used and the first one is least recently used. In an LRUscheduling system we favour the least recently used element for the next scheduling.

Write a class that implements such a data structure.

Exercise 14.9. Implement a queue data structure on a fixed size list. Initially, the head and

105

Page 106:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

tail references point to index 0. Enqueuing occurs at the tail causing the tail reference to increment.Dequeuing occurs at the head causing the head reference to decrease. If required and possible the tailand head may wrap around the end of the array. In particular, it is possible for the tail to refer toan index that is larger than the index of the head reference.

Consider the following example for an array of size 3.

1. Initially: head = tail = 0; Queue = {None,None,None}.

2. Enqueue 4: head = 0, tail = 0, Queue = {4, None,None}

3. Enqueue 2: head = 0, tail = 1, Queue = {4, 2, None}

4. Enqueue 6: head = 0, tail = 2, Queue = {4, 2, 6}

5. Now the queue is full.

6. Dequeue: head = 1, tail = 2, Queue = {None, 2, 6}

7. Dequeue: head = 2, tail = 2, Queue = {None,None, 6}

8. Enqueue 29: head = 2, tail = 0, Queue = {29, None, 6}

9. Dequeue: head = 0, tail = 0, Queue = {29, None,None}

Exercise 14.10. Design and implement a binary search tree for 1-dimensional intervals in theintegers abiding by the following principles.

1. Define the following relation on intervals in the Reals. Let a = [x1, x2] and b = [x3, x4] be twointervals in the Reals.

(a) if x1 < x3 then a < b.

(b) if x1 = x3 then:

i. if x2 < x4 then a < b.ii. if x2 = x4 then a = b.

2. Let a and b be as above such that a < b according to the relation above and let a and b intersectin a non-trivial manner (i.e., not in a single point or equality). The fragment intervals of aand b are defined as follows.

(a) if x1 < x3 ≤ x2 ≤ x4, then the fragments are [x1, x3], [x3, x2], [x2, x4], where degenerateintervals (i.e., ones whose ends coincide forming a single point) are omitted.

(b) if x1 < x3 < x4 ≤ x2, then the fragments are [x1, x3], [x3, x4], [x4, x2], where degenerateintervals (i.e., ones whose ends coincide) are omitted.

106

Page 107:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

3. Implementation requirement: Implement a binary search tree according to the above relationkeeping all intervals in the leafs and not in internal nodes.

4. Implementation requirement: Your tree implementation is to to support the following behaviour.If an interval a is being added to the tree and a non-trivial intersection (i.e., not just a singleend point or complete equivalence) between it and an already present interval b is detected, thenthe following takes place:

(a) a is not added;

(b) b is removed;

(c) their (non-degenerate) fragments are added.

Exercise 14.11. A dictionary d is defined as follows.1 >>> d = {1 : "abc", 2 : "def", 3: "ghi",2 ... 4: "jkl", 5:"mno", 6: "pqr", 7:"stu",3 ... 8:"vwx", 9:"yz"}

Write a function that receives as an argument a list of numbers l each in the range [1, 9] generate allpossible words whose ith letter corresponds to one of the letters that the ith number of l is mappedto.

For instance, if l = [5,3] then the possible words are

mg,mh,mi,ng,nh,ni,og,oh,oi

In class we have spoken of a lexicographic counter type approach for this problem. Here is asketch of this solution.

1 #!/usr/bin/env python323 d = {1 : "abc", 2 : "def", 3: "ghi",4: "jkl", 5:"mno", 6: "pqr", 7:"stu",8:"vwx", 9:"yz"}45 def f(l):67 if len(l) == 0:8 return None9

10 letterSets = [d[key] for key in l]1112 words = []1314 counter = [0 for i in range(len(l))]1516 while(hasNext(counter)): #implement hasNext()17 newWord = [letterSets[i][counter[i]] for i in range(len(l))]18 words.append(str(newWord))19 next(counter) # implement next()

Listing 5: Partial solution for Exercise 14.11

107

Page 108:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Exercise 14.12. Design and implement a linear time algorithm that removes the occurrences ofthe character ’A’ and character ’R’ from a list, shift the entries of the list to the left to make up forremoved occurrences if necessary. If shifting takes place put the number 0 in all of the free spacescreated at the end of the list due to shifting. In particular the length of the list is not to be modified.

The algorithm must be in-place, that is, only O(1) additional space is allowed; none of the removalmethods of class list are allowed to be used.

For the instance ["A","B","R","T","L","A"] your algorithm should produce ["B","T","L",0,0,0].

1 #!/usr/bin/env python32 def f(theList):34 LEN = len(theList)5 p = 06 for i in range(LEN):7 if theList[i] == "A" or theList[i] =="R":8 p+=19 else:

10 theList[i-p] = theList[i]11 for i in range(LEN - p, LEN):12 theList[i]=01314 return theList

Listing 6: Proposed solution for Exercise 14.12

Exercise 14.13. Let x = {x0, . . . , xn−1} be a list. Let a = {a0, . . . , an−1} be a list of integerssatisfying ai ∈ [0, n− 1] for all i such that a is a permutation of [0, n− 1]. Design and implement analgorithm for rearranging the values of x according to the order imposed by the permutation a. Thatis, xi should appear in position ai for every i. The algorithm is allowed only O(1) additional space.

For example, let x = {17, 5, 1, 9} and let a = {2, 1, 3, 0}; then the outcome should be x ={9, 5, 17, 1}.

In the proposed solution we do alter the list a; the algorithm is quadratic (why?). During classone of the students proposed an O(n log n) algorithm in which one sorts a and moves the entries ofx according to the entry of a that these correspond to. In this type of solution the entries of x willhave to move when the entries of a move as we are not allowed to recall the original associations inan additional map due to the space restriction. We do not present this solution. Instead we preseta less complicated solution in which we iterate the cycles of the permutation a and shift the entriesin each cycle.

1 #!/usr/bin/env python32 def printPermutation(x,a):3 j = 04 start = 0 #start of the current cycle in the permutation5 cnt = 0 # count number of exchanges6 temp2 = x[j]7 while cnt < len(x) and start >= 0:8 temp1 = temp29 temp2 = x[a[j]]

10 x[a[j]] = temp1 #make the replacement

108

Page 109:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

11 cnt+=1 #count the replacement12 j = a[j] #move to the next13 if j == start: # if a cycle has ended14 markCycle(a,start) #mark this cycle we just iterated through15 j = start = nextCycle(a) # go to the next cycle (if any)16 if start >= 0: # if there is a new cycle17 temp2 =x[j]18 return x192021 def markCycle(a,start):22 i= start23 while a[i] != -1:24 next = a[i]25 a[i] = -126 i = next272829 def nextCycle(a):30 for i in range(len(a)):31 if a[i] >= 0:32 return i33 else:34 return -1

Listing 7: Proposed solution for Exercise 14.13

Exercise 14.14. Given an array of integers with no duplicates, design and implement an algo-rithm that finds the length of the largest interval that has all its members in the given list.

For instance, for {1, 7, 4, 6, 3, 10, 2} the answer would be the interval [1, 4].

In this exercise led d denote a hash map mapping integer values (of the array) to intervalscontaining them. More precisely, the invariant that d maintains is that a value a (which is a key)points to a list of integers that form an interval that either ends or begins with a. The followingalgorithm scans the array while maintaining the above invariant for d.

1. While there are additional elements to consider do:

(a) Let e be the current element being considered.

(b) Make two queries to d: is e− 1 a key? And is e+ 1 a key? (as there are no duplicates ecannot be a key).

(c) If neither e− 1 nor e+1 is a key then add e as a new key whose value is a list containinga single element: the index of e in the array.

(d) If e− 1 is a key and e+ 1 is not a key then:

i. Add e+ 1 as a new key whose value is the list d[e-1] with e appended to the endof it.

(e) If e+ 1 is a key and e− 1 is not a key then:

i. Complete this on your own

109

Page 110:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

(f) If e+ 1 and e− 1 are keys then:

i. Complete this on your own

2. Scan the values of d and return the largest interval found.

The remaining cases are left to the reader. Let us concentrate on the case considered above thate− 1 is a key while e+ 1 is not a key and argue its correctness.

In this case, we add e as a new key and assign it the list d[e-1] ∪ {e} with e appearing atthe end. Assume towards a contradiction that in d[e-1] e − 1 does not appear at the end of thelist d[e-1]. Then by the invariant of d (i.e., induction hypothesis) e − 1 is the first element ofe− 1 (note that the basis of this induction is trivial). Also, by the induction hypothesis d[e-1] isan interval starting with e − 1. As e is considered for the first time (as there are no duplicates) itfollows that d[e-1] is in fact a singleton consisting only of e− 1; and the claim follows.

Exercise 14.15. Let A be an integer array of length n of integers with no duplicates, and let kbe an integer (k need not be present in A).

1. Find all indices 0 ≤ i, j ≤ n− 1 satisfying

A[i] + A[j] =k.

2. What if we insist on i 6= j?

We propose a solution for the first part of this problem operating in O(n) time on average usinghash maps. Here is a first attempt at the solution.

1 #!/usr/bin/env python3234 def findRepresentations(A,k):5 if len(A) == 0:6 return None78 map = {}9

10 #create a hash map whose keys are k-A[i] for each i and11 #the value is the index i12 for i in range(len(A)):13 map[k-A[i]] = i # populate the map1415 solution = [] #prepare to build the solution1617 for i in range(len(A)):18 if A[i] in map: # if the number A[i} is a key then we pick up the index19 solution.append((i,map[A[i]]))2021 return solution

The problem with this solution is that if the pair of indices (i, j) forms a solution, then thealgorithm above will add (i, j) and (j, i) to the list of solutions.

110

Page 111:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 print(findRepresentations([2,5,1,8,6,3],9))2 -------------------------------------------------------------3 Output:45 [(2, 3), (3, 2), (4, 5), (5, 4)]

Let us identify the reason that the algorithm does that more clearly. Suppose k = 5 and that {2, 3} ⊆A. Then the map will contain the associations (5 − 2 = 3, index of 2) and (5 − 3 = 2, index of 3).In the second scan both 2 and 3 are keys and this is the reason for the double reporting in the finalsolution.

If (5 − 2 = 3, index of 2) is an association in the map then we should not add the mapping(5− 3 = 2, index of 3). We do this next.

1 #!/usr/bin/env python3234 def findRepresentations(A,k):5 if len(A) == 0:6 return None78 map = {}9

10 for i in range(len(A)):11 if A[i] not in map: # this was added12 map[k-A[i]] = i1314 solution = []1516 for i in range(len(A)):17 if A[i] in map:18 solution.append((i,map[A[i]]))192021 return solution

Indeed now we have the following.1 print(findRepresentations([2,5,1,8,6,3],9))2 -------------------------------------------------------------3 Output:45 [(3, 2), (5, 4)]

The second part of the exercise asks us to prevent the following situation.1 print(findRepresentations([2,6,7,4,1],8))2 -------------------------------------------------------------3 Output:45 [(1, 0), (3, 3), (4, 2)]

That is, we should not produce pairs (i, j) with i = j. One way to do this would be using thefollowing list comprehension.

1 print([e for e in findRepresentations([2,6,7,4,1],8) if e[0] != e[1]])2 -----------------------------------------------------------------3 Output:45 [(1, 0), (4, 2)]

111

Page 112:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

This is clearly the expensive way of doing this (why?). Here is a more refined way of achieving this.1 #!/usr/bin/env python3234 def findRepresentations(A,k):5 if len(A) == 0:6 return None78 map = {}9

10 for i in range(len(A)):11 if A[i] not in map:12 map[k-A[i]] = i1314 solution = []1516 for i in range(len(A)):17 if A[i] in map and i != map[A[i]]: # condition added here18 solution.append((i,map[A[i]]))192021 return solution

Exercise 14.16. Given an array of numbers with no duplicates rearrange it into alternatingform as follows. Suppose the array is 3, 5, 7, 8, 4, 10; the output should be 3, 5, 4, 8, 7, 10; note that3 < 5 > 4 < 8 > 7 < 10.

Consider the following algorithm for this exercise. Let A denote an algorithm that receives threedistinct integers, say a1, a2, a3 and return a permutation of those, say b1, b2, b3 satisfying b1 < b2 > b3.Such an algorithm requires O(1) time.

To solve the exercise above, take a "window" of fixed size 3 initially set to the first three elementsof the array. "Slide" the window along the array by advancing it by 1 at each time. For each "slide"of the "window" apply the algorithm A above.

Exercise 14.17. Let s and t be two strings. The interpolation of s and t is the set of all stringseach comprised of the characters appearing in s and t and respecting the order of s and t; that is,characters of s, say a and b that in s appear a after b will also appear in this order (i.e., a after b)in any string member of the interpolation. This holds for the characters of t as well.

For instance, let s = AB and t = CD, then their interpolation is the set of strings:ABCDACBDACDBCADBCDAB

As another example, let s = ABC and t = DE, then their interpolation consists of:ABCDEABDCEABDEC

112

Page 113:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

ADBECADEBCDAEBCDEABC

Design and implement an algorithm that produces the interpolation of two strings.

This is an exercise that is very similar to the lexicographic counter exercise we met in one ofthe previous exercises. Indeed, consider the string s = AB given in the example. To interpolatethis with the string t = CD we need only consider the following problem: find all arrangements ofs within an array of size 4 so that the array consists of either spaces or of characters of s and thatthe characters of s appear according to the order these have in s. That is we would like to generatethe list of strings:

AB##A#B#A##B

where here # represents a space. Once this list of strings is given we may iterate it and fill in tin-order to attain the final interpolation.

Exercise 14.18. Design and implement a data structure that allows for the addition of integersand supports the retrieval of the current median in O(1) time. (Note: here addition means addinginto the data structure not addition of numbers).

One solution that has been proposed by many students was to maintain the set of integers in asorted list or array and then the median is easily retrieved in O(1) time. However, such a solutionwould entail a linear time addition operation. The solution we propose here requires logarithmiccost for the addition operation. We maintain two priority queues: a minimum heap for the upperhalf of the set and a maximum heap for the lower half of the set. The need to keep these two heapsas balanced as possible introduces some case analysis into the code; this can be optimised but weomit such efforts here.

Let us assume the existence of a class called Heap such that Heap(type = "min") andHeap(type = "max") construct a minimum and maximum heap, respectively. Furthermore, letus assume a simple interface for this class as follows. A method insert() adds elements to theheap w.r.t. to its min/max property. A method remove() that returns and removes the min/maxelement; if such does not exist it returns None. A method peek() that returns without removingthe min/max element of the heap; if such does not exist it returns None. And a method size()that return the size of the heap.

1 #!/usr/bin/env python3234 class Medianmaintainer:56 def __init__(self):7 self.lowerHalf = Heap(type = "max")8 self.upperHalf = Heap(type = "min")9 self.size = 0

10

113

Page 114:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

11 def add(self,e):1213 #shorter names14 lowerHalf = self.lowerHalf15 upperHalf = self.upperhalf1617 #Commonly used values below18 upperTop = upperHalf.peek()19 upperSize = upperHalf.size()20 lowerTop = lowerHalf.peek()21 lowerSize = lowerHalf.size()2223 # the algorithm starts here2425 # if the two halves are balanced26 if upperSize == lowerSize:27 if upperSize == 0:28 lowerhalf.insert(e)29 else: #upperSize == lowerSize > 030 if e < upperTop:31 lowerHalf.insert(e)32 elif e > upperTop:33 upperHalf.insert(e)34 else: # e == upperTop35 pass # avoid duplicates363738 #if the two halves are unbalanced39 #we verify which half is bigger40 elif upperSize > lowerSize:41 if e < upperTop:42 lowerHalf.insert(e)43 elif e > upperTop:44 lowerHalf.insert(upperHalf.remove())45 upperHalf.insert(e)46 else: # e == upperTop47 pass # ifnore duplicates48 else: # upperSize < lowerSize49 if e >lowerTop:50 upperHalf.insert(e)51 elif e < lowerTop:52 upperHalf.insert(lowerHalf.remove())53 lowerHalf.insert(e)54 else: # e == lowerTop55 pass #ifnore duplicates5657 self.size += 1585960 def getMedian(self):61 if self.isEven():62 return (lowerHalf.peek() + upperHalf.peek()) / 263 else: # size has odd parity64 if lowerHalf.size() > upperHalf.size():65 return lowerHalf.peek()66 else: # it must be that upperHalf.size() > lowerHalf.size()67 #as parity is odd68 return upperHalf.peek()6970 def isEven(self):71 return self.size % 2 == 0

Exercise 14.19. Let A be an array/list of integers.

1. Suppose A consists of all numbers in [1, 100] except for one. Design and implement a linear

114

Page 115:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

time algorithm for finding the missing number.

2. Suppose now that A consists of all numbers in the range [1, 106] except for one. Does youralgorithm still work? Redesign if not.

3. Assume next that A consists of all numbers in [1, 100] except for two. Design and implementan algorithm that finds the two missing numbers.

Here is our proposed solution for the first part of this exercise.1 #!/usr/bin/env python3234 def product(lst):5 if len(lst) == 0:6 return 07 solution = 18 for e in lst:9 solution = solution * e

10 return solution1112 def findMissing(lst):13 return int(product([i for i in range(1,101)])/product(lst))1415 l1 = [i for i in range(1,49)] # missing 4916 l2 = [i for i in range(50,101)]17 l1.extend(l2)1819 print(findMissing(l1))

Now, in the second part the range could reach up to 106; the above algorithm will have tocalculate 106! which is a bit inefficient. Here is our proposed solution in this case.

1 def findMissing(lst):2 map = [False for i in range(0,max(lst)+1)]3 for e in lst:4 map[e] = True5 map[0] = True #ignore index 06 return map.index(False)

Using a dictionary in this case would be more expensive (why?).Proceeding to the last part we now have two numbers missing in the range [1, 100] and are asked

to find those numbers.1 #!/usr/bin/env python323 from math import sqrt as root45 def product(lst):6 if len(lst) == 0:7 return 08 solution = 19 for e in lst:

10 solution = solution * e11 return solution1213 #find roots of c[0]x^2 + c[1] x + c[2]14 def quadraticRoots(c):15 roots = [(-c[1] - root(c[1]**2 - 4 * c[0]*c[2]))/(2*c[0]), \

115

Page 116:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

16 (-c[1] + root(c[1]**2 - 4 * c[0]*c[2]))/(2*c[0]) ]17 roots.sort()18 return roots1920 def findTwoMissing(lst):21 L= [i for i in range(1,101)]22 productOfMissing = product(L) // product(lst)23 sumOfMissing = sum(L) - sum(lst)24 missing1 = int(quadraticRoots([-1,sumOfMissing,-productOfMissing])[1])25 missing2 = sumOfMissing - missing126 return [missing2,missing1]

Exercise 14.20. Design a data structure that allows to push and pop integers in LIFO orderand supports retrieval of the least element and largest element in the current collection of integersin O(1) time.

We provide a solution based on three traditional stacks. One stack keeps track of the elements;another of the minimum elements; and the third tracks maximum elements.

1 #!/usr/bin/env python323 class MinMaxStack:45 class Stack:67 def __init__(self):8 self.theStack = []9

10 def push(self,e):11 self.theStack.append(e)1213 def pop(self):14 if not self.isEmpty():15 return self.theStack.pop()16 else:17 return None1819 def peek(self):20 if not self.isEmpty():21 return self.theStack[len(self.theStack)-1]22 else:23 return None2425 def isEmpty(self):26 return len(self.theStack) == 02728 def __init__(self):29 self.stack = MinMaxStack.Stack()30 self.minStack = MinMaxStack.Stack()31 self.maxStack = MinMaxStack.Stack()323334 def push(self,e):35 self.stack.push(e)36 max = self.maxStack.peek()37 min = self.minStack.peek()38 if max is None or e > max:39 self.maxStack.push(e)40 if min is None or e < min:41 self.minStack.push(e)4243 def pop(self):44 if not self.isEmpty():

116

Page 117:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

45 e = self.stack.pop()46 if e == self.maxStack.peek():47 self.maxStack.pop()48 if e == self.minStack.peek():49 self.minStack.pop()50 return e51 else:52 return None5354 def max(self):55 return self.maxStack.peek()5657 def min(self):58 return self.minStack.peek()5960 def isEmpty(self):61 return self.stack.isEmpty()

Exercise 14.21. A sequence of integers (possibly with repetitions) A = a0, a1, . . . , an is calledalternating if either a0 < a1 > a2 < . . . or a0 > a1 < a2 > . . . holds.

Given a list of integers (possibly with repetitions), design a linear time algorithm that finds alongest alternating subsequence in the list. The space limitation for the algorithm is O(1).

For instance, for the list [2,-10,-3,0,1,-1,2,5,6,9,8] possible solutions are: [2,-10,1,-1,9,8]and [2,-3,1,-1,6,8].

The algorithm proposed below for this exercise first decomposes the list into ascending anddescending sequence as to locate local extrema points. For instance, consider the list:

[2,2,2,2,-10,-10,-3,0,-1,1,1,1,1,-1,2,5,6,9,10,11,12].

We decompose this array into subsequences as follows:

2,2,2,2 flat-10,-10 flat-3,0 ascending-1 single point1,1,1,1,1 flat-1 single point2,5,6,9,10,11,12 ascending11,10,9 descending

Suppose we had no flat sequences and that in the decomposition only ascending and descendingsequences appear. Then, the ends of each such sequence is a local extrema point and we maysimply collect those into our final alternating sequence. Let us introduce flat sequence back intothe problem. First, we may agree that flat sequence and single points are conceptually the sameas far as our algorithm is concerned. Single points in the decomposition must always be a localextrema point. Note that, to get an alternating subsequence we need not decide wither a point islocal minima or a local maxima, only that it is an extrema point.

Conceptually, the algorithm for this problem has two steps: decompose and then collect extremapoints lying at the end of the resulting subsequences. In the code below we stream line these too

117

Page 118:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

task in the sense that as soon as a subsequence of the decompositions produced we immediatelycollect its associated extrema point. Note that, the decomposition is carried out implicitly; thesubsequences are not actually generated.

1 #!/usr/bin/env python323 def alternating(lst):4 LEN = len(lst)5 if LEN == 0 :6 return None7 if LEN == 1:8 return lst9

10 # we may assume LEN > 11112 #set up initial direction13 if lst[0] <= lst[1]:14 dir = "<=" #going up15 else: # if lst[0] == lst[1] else will be executed as well16 dir = ">=" #goin down171819 solution = []20 i = 121 while i < LEN:22 if dir == "<=":23 peak, i , dir = getNextPeak(lst,i)24 if i <LEN:25 solution.append(peak)26 continue27 if dir == ">=":28 valley, i, dir = getNextValley(lst,i)29 if i <LEN:30 solution.append(valley)31 continue3233 solution.append(lst[LEN-1]) #pick up element after last34 #peak or valley35 return solution3637 def getNextPeak(lst,i):38 LEN = len(lst)39 while i < LEN and lst[i-1] <= lst[i]:40 i += 141 if i >= LEN:42 return [None,i,None]43 else: #lst[i-1]> lst[i]44 return [lst[i-1], i , ">="]4546 def getNextValley(lst,i):47 LEN = len(lst)48 while i < LEN and lst[i-1] >= lst[i]:49 i += 150 if i >= LEN:51 return [None,i,None]52 else: #lst[i-1] < lst[i]53 return [lst[i-1], i , "<="]5455 #testing56 print (alternating([2,2,2,2,-10,-10,-3,0,-1,1,1,1,1,-1,2,5,6,9,10,11,12,11,10,9]))5758 ---------------------------------------------------------59 Output:6061 [2, -10, 0, -1, 1, -1, 12, 9]

118

Page 119:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

Exercise 14.22. A list consisting of zeros and ones is given. Design and implement an algorithmto sort it while adhering to the following limitations.

• The algorithm should operate in linear time.

• The algorithm uses O(1) space.

• Count sorting is forbidden; i.e., you are not allowed to count how many ones and zeros arepresent in the list.

For instance, if the list given is [0,1,1,0,0,1,0] then the should be [0,0,0,0,1,1,1].

Here is a proposed solution.1 #!/usr/bin/env python323 def sortZerosOnes(lst):4 LEN = len(lst)5 if LEN <= 1:6 return lst78 head = 09 tail = LEN-1

1011 while head < tail:12 RES = lst[head] - lst[tail]13 if RES== 1:14 lst[head], lst[tail] = lst[tail], lst[head]15 head += 116 tail -= 117 elif RES == -1:18 head += 119 tail -= 120 else: # RES == 021 if lst[head] == 1 and lst[tail] == 1:22 tail -= 123 continue24 if lst[head] == 0 and lst[tail] == 0:25 head +=126 continue2728 return lst

Exercise 14.23. An integer n is said to lie strictly between two distinct integers x and y if eitherx < n < y or y < n < x.

Given an array/list A, two indices i and j are said to be adjacent if no member of A lies strictlybetween their associated values A[i] and A[j]. For instance in the array A

1 A[0] = 02 A[1] = 33 A[2] = 34 A[3] = 75 A[4] = 56 A[5] = 37 A[6] = 118 A[7] = 1

119

Page 120:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

the following pairs of indices are adjacent: (0, 7), (1, 4), (1, 7), (2, 4), (2, 7), (3, 4), (3, 6), (4, 5),(5, 7). Consider the pair (4, 5) for instance, we have that A[4] != A[5] and no member of A liesstrictly between these values.

Design an implement an algorithm that for a given list of integers finds all its adjacent indices.Running time should be O(n log n) and space complexity O(n) where n is the length of the list.

Here is a possible algorithm of this problem.

1. Sort the array.

2. Let S be the resulting sorted array. Truncate S to not contain duplicates.

3. Construct a hash map d as follows.

(a) For each index i map (S[i], S[i+1]) to the list [k, j] where k is the index of S[i] in A andj is the index of S[i+ 1] in A.

4. Scan A. Let a be the element of A currently considered. Query d to find the sole intervalcontaining a. If a is not an endpoint of the interval add the index of a to the list associatedwith the interval.

5. Scan d. Report any interval that has only two indices in its associated lists.

There are several implementation issues here. The easy one is how to find indices in A for valuesin S. This can be solved with an additional hash map constructed prior to sorting. Details are leftto the reader.

The more difficult problem is that we would like to query d with single numbers while its keysare intervals. There are several solutions to this. Perhaps the simplest one is to alter d so thatinstead of keeping (S[i], S[i+ 1]) as a key we keep two keys S[i] and S[i+ 1] and make those pointto the same list of indices. The details are left to the reader.

Exercise 14.24. Let A be an array or a list of integers of length n, and let B be an array or alist of integers of length m. Let k ≥ 0 be an integer.

1. Suppose A is not sorted. Devise and implement an O(n log n + k log n) algorithm to find thekth smallest element in A, if such exists. Space complexity O(n).

2. Suppose A is not sorted. Adapt the Quicksort algorithm to find the kth smallest element in A,if such exists. Space complexity O(1) (using the time analysis of Quicksort this would yieldan algorithm whose running time expectancy is linear but worst case cost could be as high asO(n2) - like Quicksort)

3. Suppose both A and B are sorted in, say, increasing order. Devise and implement an O(log n+logm) algorithm for finding the kth smallest element in A ∪B. Space complexity O(1)

120

Page 121:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

The first part of this exercise can be solved by constructing the appropriate heap and thenextracting from it k times.

The second part of this exercise asks for an adaptation of Quicksort. Here is a sketch of howsuch an adaptation may look like.

1 while True:2 pivotIndex=random.randint(left, right)3 pivotIndex=partition1(arr, left, right, pivotIndex)4 rank=pivotIndex-left+15 if rank==k:6 return arr[pivotIndex]7 elif k<rank:8 return kthLargest1(arr, left, pivotIndex-1, k)9 else: return kthLargest1(arr, pivotIndex+1, right, k-rank)

We leave the task of making this precise to the reader (i.e., completing the undefined functions).The idea behind the third part of this exercise is to use the fact that the arrays are sorted and

to conduct a binary search in both of them at the same time.

Exercise 14.25. Write a program that employs two threads. One prints the even numbers andthe other prints the odd numbers. The threads should print an entire interval of integers specified bystart and end (inclusive of the ends). The printing should be done with respect to the order of theintegers.

For instance if start = 2 and end = 5 then the output should be:1 Even printing: 22 Odd printing: 33 Even printing: 44 Odd printing: 5

We offer several solutions. The first of which employs condition variables.1 #!/usr/bin/env python3234 import threading56 class EvenPrinter (threading.Thread):789 def __init__(self, condition, start , end, turn):

10 threading.Thread.__init__(self)11 self.cond = condition12 self.turn = turn13 if start % 2 == 0:14 self.turn[0] = "Even"15 self.cur = start16 else:17 self.turn [0] = "Odd"18 self.cur = start +119 if end % 2 == 0:20 self.end = end+121 else:22 self.end = end2324 def run(self):2526

121

Page 122:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

27 while self.cur < self.end:28 self.cond.acquire()2930 try:31 while self.turn[0] != "Even":32 self.cond.wait()3334 #now self.turn[0] == "Even"35 print("EvenPrinter:", self.cur)36 self.cur+=237 self.turn[0] = "Odd"3839 finally:40 self.cod.notify()41 self.cond.release()4243 class OddPrinter (threading.Thread):444546 def __init__(self, condition, start , end, turn):47 threading.Thread.__init__(self)48 self.cond = condition49 self.turn = turn50 if start % 2 == 0:51 self.turn [0] = "Even"52 self.cur = start+153 else:54 self.turn [0] = "Odd"55 self.cur = start56 if end % 2 == 0:57 self.end = end58 else:59 self.end = end+16061 def run(self):626364 while self.cur < self.end:65 self.cond.acquire()6667 try:68 while self.turn[0] != "Odd":69 self.cond.wait()7071 #now self.turn[0] == "Odd"72 print("OddPrinter:", self.cur)73 self.cur+=274 self.turn[0] = "Even"7576 finally:77 self._cond.notify()78 self.cond.release()798081 # main program8283 condition = threading.Condition()84 turn = [None]8586 evenPrinter = EvenPrinter(condition, 2,20,turn)87 oddPrinter = OddPrinter(condition,2,20,turn)8889 oddPrinter.start()90 evenPrinter.start()91 ----------------------------------------------92 Output:

122

Page 123:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

9394 EvenPrinter: 295 OddPrinter: 396 EvenPrinter: 497 OddPrinter: 598 EvenPrinter: 699 OddPrinter: 7

100 EvenPrinter: 8101 OddPrinter: 9102 EvenPrinter: 10103 OddPrinter: 11104 EvenPrinter: 12105 OddPrinter: 13106 EvenPrinter: 14107 OddPrinter: 15108 EvenPrinter: 16109 OddPrinter: 17110 EvenPrinter: 18111 OddPrinter: 19112 EvenPrinter: 20

The second solution employs events.1 #!/usr/bin/env python3234 import threading56 class EvenPrinter (threading.Thread):789 def __init__(self, start , end, can_i_print, can_other_print):

10 threading.Thread.__init__(self)11 self.can_i_print = can_i_print12 self.can_other_print = can_other_print1314 if start % 2 == 0:15 self.cur = start16 self.can_i_print.set()17 else:18 self.cur = start +119 if end % 2 == 0:20 self.end = end+121 else:22 self.end = end2324 def run(self):2526 while self.cur < self.end:2728 self.can_i_print.wait()2930 print("Even printer:", self.cur)31 self.cur+=23233 self.can_i_print.clear()34 self.can_other_print.set()353637 class OddPrinter (threading.Thread):383940 def __init__(self, start , end, can_i_print, can_other_print):41 threading.Thread.__init__(self)42 self.can_i_print = can_i_print43 self.can_other_print = can_other_print

123

Page 124:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

44 if start % 2 == 0:45 self.cur = start+146 else:47 self.can_i_print.set()48 self.cur = start49 if end % 2 == 0:50 self.end = end51 else:52 self.end = end+15354 def run(self):5556 while self.cur < self.end:5758 self.can_i_print.wait()5960 print("Odd printer:", self.cur)61 self.cur+=26263 self.can_i_print.clear()64 self.can_other_print.set()65666768 # main program6970 even_can_print = threading.Event()71 odd_can_print = threading.Event()7273 evenPrinter = EvenPrinter(2,20,even_can_print, odd_can_print)74 oddPrinter = OddPrinter(2,20,odd_can_print, even_can_print)7576 oddPrinter.start()77 evenPrinter.start()78 -------------------------------------------------------------------------------79 Output:8081 Even printer: 282 Odd printer: 383 Even printer: 484 Odd printer: 585 Even printer: 686 Odd printer: 787 Even printer: 888 Odd printer: 989 Even printer: 1090 Odd printer: 1191 Even printer: 1292 Odd printer: 1393 Even printer: 1494 Odd printer: 1595 Even printer: 1696 Odd printer: 1797 Even printer: 1898 Odd printer: 1999 Even printer: 20

The solutions so far were rather unpleasant design wise as the threads had to be given specificinformation about other threads in the system. This to ensure correct turn alternation. We offerthe following solution then.

1 #!/usr/bin/env python3234 import threading

124

Page 125:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

567 class NumberGenerator:89 def __init__(self, start,end):

10 self.cur = start11 self.end = end12 self.even_can_print = threading.Event()13 self.odd_can_print = threading.Event()1415 if self.cur % 2 == 0:16 self.even_can_print.set()17 else:18 self.odd_can_print.set()1920 def evenPrint(self):21 if self.cur > self.end:22 return "STOP"2324 self.even_can_print.wait()25 print("Even printing:", self.cur)26 self.cur+=127 self.even_can_print.clear()28 self.odd_can_print.set()2930 def oddPrint(self):31 if self.cur > self.end:32 return "STOP"3334 self.odd_can_print.wait()35 print("Odd printing:", self.cur)36 self.cur+=137 self.odd_can_print.clear()38 self.even_can_print.set()3940 class PrinterThread (threading.Thread):414243 def __init__(self, src, type):44 threading.Thread.__init__(self)45 self.src = src46 self.type = type4748 def run(self):4950 flag = None5152 while flag != "STOP":53 if self.type == "Even":54 flag = self.src.evenPrint()55 continue56 if self.type == "Odd":57 flag = self.src.oddPrint()58596061 # main program6263 numSrc = NumberGenerator(2,20)6465 even = PrinterThread(numSrc,"Even")66 odd = PrinterThread(numSrc, "Odd")6768 odd.start()69 even.start()70 -----------------------------------------------------------

125

Page 126:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

7172 Output:7374 Even printing: 275 Odd printing: 376 Even printing: 477 Odd printing: 578 Even printing: 679 Odd printing: 780 Even printing: 881 Odd printing: 982 Even printing: 1083 Odd printing: 1184 Even printing: 1285 Odd printing: 1386 Even printing: 1487 Odd printing: 1588 Even printing: 1689 Odd printing: 1790 Even printing: 1891 Odd printing: 1992 Even printing: 2093 Odd printing: 21

Here we encounter a problem. The last line of the output of the last program should not have beenprinted Odd printing: 21; indeed in the method oddPrint() we are to stop if self.cur> self.end. The reason for this is the following. At a certain point when self.cur is equal to20 the odd thread has the CPU attention. As it notices that self.odd_can_print is not set itwait()s. At which point the even thread takes over prints 20 and increments self.cur to 21.By now the odd thread has passed the statement checking whether self.cur > self.end andprints 21.

We propose the following fix (which will work but will not be good still).1 #!/usr/bin/env python3234 import threading567 class NumberGenerator:89 def __init__(self, start,end):

10 self.cur = start11 self.end = end12 self.even_can_print = threading.Event()13 self.odd_can_print = threading.Event()1415 if self.cur % 2 == 0:16 self.even_can_print.set()17 else:18 self.odd_can_print.set()1920 def evenPrint(self):2122 self.even_can_print.wait()23 try:24 if self.cur > self.end:25 return "STOP"26 finally:27 self.even_can_print.clear() # not really needed28 self.odd_can_print.set()

126

Page 127:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

2930 self.even_can_print.wait()31 print("Even printing:", self.cur)32 self.cur+=133 self.even_can_print.clear()34 self.odd_can_print.set()3536 def oddPrint(self):37 self.odd_can_print.wait()38 try:39 if self.cur > self.end:40 return "STOP"41 finally:42 self.odd_can_print.clear() # not really needed43 self.even_can_print.set()444546 self.odd_can_print.wait()47 print("Odd printing:", self.cur)48 self.cur+=149 self.odd_can_print.clear()50 self.even_can_print.set()515253 class PrinterThread (threading.Thread):545556 def __init__(self, src, type):57 threading.Thread.__init__(self)58 self.src = src59 self.type = type6061 def run(self):6263 flag = None6465 while flag != "STOP":66 if self.type == "Even":67 flag = self.src.evenPrint()68 continue69 if self.type == "Odd":70 flag = self.src.oddPrint()71727374 # main program7576 numSrc = NumberGenerator(2,20)7778 even = PrinterThread(numSrc,"Even")79 odd = PrinterThread(numSrc, "Odd")8081 odd.start()82 even.start()83 ------------------------------8485 Output:8687 Even printing: 288 Odd printing: 389 Even printing: 490 Odd printing: 591 Even printing: 692 Odd printing: 793 Even printing: 894 Odd printing: 9

127

Page 128:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

95 Even printing: 1096 Odd printing: 1197 Even printing: 1298 Odd printing: 1399 Even printing: 14

100 Odd printing: 15101 Even printing: 16102 Odd printing: 17103 Even printing: 18104 Odd printing: 19105 Even printing: 20106 Odd printing: 21

We see that this change did not solve the problem. Let us observe the changes we made to themethod evenPrint() of NumberGenerator. A similar change was made to the oddPrint()method as well.

1 self.even_can_print.wait()2 try:3 if self.cur > self.end:4 return "STOP"5 finally:6 self.even_can_print.clear() # not really needed7 self.odd_can_print.set()

The problem we had before that led to the threads printing beyond the threshold limit self.endis that the if clause checking this breach was performed before the call to wait() which led to ascenario in which a thread passes the check hangs on wait() mean while the condition is no longertrue.

To fix that, we make sure now that wait() is performed before the if clause. However, theproblem now is that the finally clause is mainly suited for the case that return is executed.Indeed, if this return is to be executed then we would like to turn the attention to the other thread(as performed in the finally).

But suppose now that self.cur == 20 and that it is the turn of the odd thread and that itexecutes the code of oddPrint() that is analogues to the one above. Assuming it is the turn of theodd thread it is able to pass through the wait() without blocking as it is its turn. The conditionin the if clause is false and so the return statement is not executed. The finally clause getsexecuted in which the odd thread relinquishes its turn for the even thread. The even thread prints20 and advances self.cur to 21. At this point, the odd thread passed the if clause and so thecondition is not checked again despite it being false.

Threads relinquishing their turn in the finally clause is even more problematic then passingthe self.end threshold. Consider the following situation in which self.cur == 3 and that itis the odd thread turn. The return statement does not get executed and in the finally clausethe odd thread relinquishes its turn to the even thread. The even thread can now pick up and print3 as it may be blocked on the second call to wait() found in evenPrint(). This more disastrousscenario does not happen. Let us see just why by adding appropriate printing commands in thefinally clause.

1 #!/usr/bin/env python32

128

Page 129:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

34 import threading567 class NumberGenerator:89 def __init__(self, start,end):

10 self.cur = start11 self.end = end12 self.even_can_print = threading.Event()13 self.odd_can_print = threading.Event()1415 if self.cur % 2 == 0:16 self.even_can_print.set()17 else:18 self.odd_can_print.set()1920 def evenPrint(self):2122 self.even_can_print.wait()23 try:24 if self.cur > self.end:25 return "STOP"26 finally:27 print("even", self.cur)28 self.even_can_print.clear() # not really needed29 self.odd_can_print.set()3031 self.even_can_print.wait()32 print("Even printing:", self.cur)33 self.cur+=134 self.even_can_print.clear()35 self.odd_can_print.set()3637 def oddPrint(self):38 self.odd_can_print.wait()39 try:40 if self.cur > self.end:41 return "STOP"42 finally:43 print("odd",self.cur)44 self.odd_can_print.clear() # not really needed45 self.even_can_print.set()4647 self.odd_can_print.wait()48 print("Odd printing:", self.cur)49 self.cur+=150 self.odd_can_print.clear()51 self.even_can_print.set()525354 class PrinterThread (threading.Thread):555657 def __init__(self, src, type):58 threading.Thread.__init__(self)59 self.src = src60 self.type = type6162 def run(self):6364 flag = None6566 while flag != "STOP":67 if self.type == "Even":68 flag = self.src.evenPrint()

129

Page 130:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

69 continue70 if self.type == "Odd":71 flag = self.src.oddPrint()72737475 # main program7677 numSrc = NumberGenerator(2,21)7879 even = PrinterThread(numSrc,"Even")80 odd = PrinterThread(numSrc, "Odd")8182 odd.start()83 even.start()84 --------------------------------------------------85 Output:8687 even 2 # in the finally clause of evenPrint88 odd 2 # in the finally clause of oddPrint89 Even printing: 290 Odd printing: 391 even 492 odd 493 Even printing: 494 Odd printing: 595 even 696 odd 697 Even printing: 698 Odd printing: 799 even 8

100 odd 8101 Even printing: 8102 Odd printing: 9103 even 10104 odd 10105 Even printing: 10106 Odd printing: 11107 even 12108 odd 12109 Even printing: 12110 Odd printing: 13111 even 14112 odd 14113 Even printing: 14114 Odd printing: 15115 even 16116 odd 16117 Even printing: 16118 Odd printing: 17119 even 18120 odd 18121 Even printing: 18122 Odd printing: 19123 even 20124 odd 20125 Even printing: 20126 Odd printing: 21127 even 22128 odd 22

This output is quite strange. Note that in finally clauses only even numbers are printed neverodd ones. Is it because the finally clause of oddPrint() does not get executed? Surely not.To understand what is going on let us consider the output

130

Page 131:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

1 even 2 # in the finally clause of evenPrint2 odd 2 # in the finally clause of oddPrint3 Even printing: 24 Odd printing: 3

The even thread starts of the program and prints self.cur == 2 in its finally clause. It thenpasses the turn to the odd thread which performs the if clause, and then performs the finally andprints 2 and passes the turn to the even thread. By now, the even thread is blocked on the secondwait() call in evenPrint(). It get attention and prints 2 as it should, advances self.cur to 3,and passes the turn to the odd thread. The odd thread by now is also blocked on the second wait()call in oddPrint(). When it receives attention now it just prints 3, advances the counter to 4,and passes the turn. From the output we see that this goes on like this throughout the program.

In the following solution attempt, the above try clause is replaced with the following.1 self.even_can_print.wait()2 if self.cur > self.end:3 self.even_can_print.clear() # not really needed4 self.odd_can_print.set()5 return "STOP"

Here, only if the if clause is executed do the thread relinquish its turn. Also, in this solution thesecond wait() call in both methods can be removed.

Let us check whether this proposal is effective. As before suppose self.cur == 20 and thatit is the turn of the odd thread. The if clause is not executed as its condition is false and so the oddthread proceeds to printing 20. We seemingly have an even worse problem then before. However, thepremise of our scenario is false. self.cur is never even when it is the turn of the odd thread. Thisis due to the fact that self.cur and the turns alternate together as both methods evenPrint()and oddPrint() are treated as critical sections.

1 #!/usr/bin/env python3234 import threading567 class NumberGenerator:89 def __init__(self, start,end):

10 self.cur = start11 self.end = end12 self.even_can_print = threading.Event()13 self.odd_can_print = threading.Event()1415 if self.cur % 2 == 0:16 self.even_can_print.set()17 else:18 self.odd_can_print.set()1920 def evenPrint(self):2122 self.even_can_print.wait()23 if self.cur > self.end:24 self.even_can_print.clear() # not really needed25 self.odd_can_print.set()26 return "STOP"

131

Page 132:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

2728 print("Even printing:", self.cur)29 self.cur+=130 self.even_can_print.clear()31 self.odd_can_print.set()3233 def oddPrint(self):34 self.odd_can_print.wait()35 if self.cur > self.end:36 self.odd_can_print.clear() # not really needed37 self.even_can_print.set()38 return "STOP"3940 print("Odd printing:", self.cur)41 self.cur+=142 self.odd_can_print.clear()43 self.even_can_print.set()444546 class PrinterThread (threading.Thread):474849 def __init__(self, src, type):50 threading.Thread.__init__(self)51 self.src = src52 self.type = type5354 def run(self):5556 flag = None5758 while flag != "STOP":59 if self.type == "Even":60 flag = self.src.evenPrint()61 continue62 if self.type == "Odd":63 flag = self.src.oddPrint()64656667 # main program6869 numSrc = NumberGenerator(2,20)7071 even = PrinterThread(numSrc,"Even")72 odd = PrinterThread(numSrc, "Odd")7374 odd.start()75 even.start()76 -----------------------------------------------------77 Output:7879 Even printing: 280 Odd printing: 381 Even printing: 482 Odd printing: 583 Even printing: 684 Odd printing: 785 Even printing: 886 Odd printing: 987 Even printing: 1088 Odd printing: 1189 Even printing: 1290 Odd printing: 1391 Even printing: 1492 Odd printing: 15

132

Page 133:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

93 Even printing: 1694 Odd printing: 1795 Even printing: 1896 Odd printing: 1997 Even printing: 20

Exercise 14.26. Write code in which k > 0 threads with id numbers operate in cyclic order.Each printing its id in its turn. This cyclic printing is repeated m > 0 times. For instance, if k = 3and m = 2 then the output should resemble the following

1 Thread 12 Thread 23 Thread 34 Thread 15 Thread 26 Thread 3

We give a solution for k = 5 and m = 3.1 #!/usr/bin/env python323 import threading456 class myThread (threading.Thread):78 def __init__(self, times, index, theSems):9 threading.Thread.__init__(self)

10 self.times = times11 self.index = index12 self.name = "Thread " + str(index)13 self.theSems = theSems1415 def run(self):1617 cur = 01819 while cur <self.times:2021 self.theSems[self.index].acquire()2223 print(self.name +" printing for the " + str(cur)+" time")24 cur+=12526 self.theSems[int((self.index +1) % len(self.theSems) )].release()2728 #main program29 theSems = [threading.BoundedSemaphore(1) for i in range(1,6)]3031 for i in range(1,len(theSems)):32 theSems[i]._value = 03334 theThreads = [myThread(3,i-1,theSems) for i in range(1,6)]3536 for t in theThreads:37 t.start()38 ------------------------------------------3940 Output:4142 Thread 0 printing for the 0 time43 Thread 1 printing for the 0 time

133

Page 134:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

44 Thread 2 printing for the 0 time45 Thread 3 printing for the 0 time46 Thread 4 printing for the 0 time47 Thread 0 printing for the 1 time48 Thread 1 printing for the 1 time49 Thread 2 printing for the 1 time50 Thread 3 printing for the 1 time51 Thread 4 printing for the 1 time52 Thread 0 printing for the 2 time53 Thread 1 printing for the 2 time54 Thread 2 printing for the 2 time55 Thread 3 printing for the 2 time56 Thread 4 printing for the 2 time

Exercise 14.27. In a system there are four threads called Thread 1, Thread 2, Thread3, and Thread 4. The task of each is to print its name. However, Thread 4 can only printprovided the previous three printed their names. Write code in which the first three threads and thefourth thread alternate as follows m > 0 times:

• Threads 1, 2, 3 print their names (in any order you wish).

• Thread 4 then prints its name.

• Thread 1, 2, 3 print their names.

• Thread 4 then prints its name.

• This should repeat m times.

A trivial solution is to force all threads to perform in a cyclic order of 1, 2, 3, 4. For the sake of thisexercise assume that it is forbidden to force such an order (or any order for that matter) on threads1, 2, and 3.

Here is one possible solution for m = 3.1 #!/usr/bin/env python323 import threading45 class Synchroniser:67 def __init__(self):89 #sync mechanisms

10 self.turn_cond = threading.Condition()11 self.calls = threading.BoundedSemaphore(3)12 self.turn_mutex = threading.BoundedSemaphore(1)13 self.run_states_mutex = threading.BoundedSemaphore(1)1415 #sync data16 self.run_states = [None, 0,0,0]17 self.turn ="123"1819 def alter_run_states(self,index):20 self.run_states_mutex.acquire()21 try:22 self.run_states[index] += 1

134

Page 135:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

23 finally:24 self.run_states_mutex.release()2526 def set_turn(self, turn):27 self.turn_mutex.acquire()28 try:29 self.turn = turn30 finally:31 self.turn_mutex.release()323334 def join(self, name):3536 if name in ["1","2","3"]:3738 self.turn_cond.acquire()39 try:40 if (self.run_states[int(name)]-1) not in self.run_states :41 self.set_turn("4")42 finally:43 self.turn_cond.notify_all()44 self.turn_cond.release()4546 return4748 if name == "4":49 self.turn_cond.acquire()50 try:51 self.set_turn("123")52 self.calls.release()53 self.calls.release()54 self.calls.release()55 return56 finally:57 self.turn_cond.notify_all()58 self.turn_cond.release()5960 def getPermission(self, name):616263 if name in ["1","2","3"]:64 self.turn_cond.acquire()65 try:66 while self.turn != "123":67 self.turn_cond.wait()68 finally:69 self.turn_cond.release()7071 self.calls.acquire()72 if (self.run_states[int(name)]- 1) in self.run_states:73 self.calls.release()74 return False75 else:76 self.alter_run_states(int(name))77 return True7879808182 if name == "4":83 self.turn_cond.acquire()84 try:85 while self.turn != "4":86 self.turn_cond.wait()87 return True88 finally:

135

Page 136:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

89 self.turn_cond.release()9091929394 class myThread (threading.Thread):9596 def __init__(self,times,name,sync):97 threading.Thread.__init__(self, name = name)98 self.sync = sync99 self.times = times

100101 def run(self):102103 cur = 1104105 while cur <= self.times:106 permission = self.sync.getPermission(self.name)107 if permission:108 print("Thread " + self.name)109 cur+=1110 self.sync.join(self.name)111112 #main program113114 sync = Synchroniser()115 threads = [myThread(3,1,sync),myThread(3,2,sync),myThread(3,3,sync),myThread(3,4,sync)]116117 for t in threads:118 t.start()119120 ------------------------------------------------121 Output:122123 Thread 1124 Thread 2125 Thread 3126 Thread 4127 Thread 3128 Thread 2129 Thread 1130 Thread 4131 Thread 2132 Thread 3133 Thread 1134 Thread 4

Exercise 14.28. Write code that manages k > 2 threads that execute in the following order.First threads 0 through k − 1 execute in increasing order, followed by the execution of threads k − 1through 0 in decreasing order. This has to repeat m > 0 times.

For instance, for k = 3 and m = 2 the order of execution should be:1 Thread 02 Thread 13 Thread 24 Thread 25 Thread 16 Thread 07 Thread 08 Thread 19 Thread 2

10 Thread 211 Thread 1

136

Page 137:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

12 Thread 0

Here is an initial solution that does not work for k = 5 and m = 3.1 #!/usr/bin/env python3234 import threading56 class myThread(threading.Thread):78 def __init__(self,sems1,sems2,index,times):9 threading.Thread.__init__(self)

10 self.sems1 = sems111 self.sems2 = sems212 self.index = index13 self.times = times1415 def run(self):1617 LEN = len(self.sems1)18 index = self.index1920 cur = 02122 while cur < self.times:2324 self.sems1[index].acquire()25 print("Thread ", index)26 if index < LEN -1:27 self.sems1[int((index +1) % LEN )].release()28 if self.index == LEN-1:29 self.sems2[index].release()30 self.sems2[index].acquire()31 print("Thread ", index)32 if index > 0:33 self.sems2[int((index -1) % LEN )].release()34 if index == 0:35 self.sems1[index].release()36 cur+=13738 #main3940 sems1 = [threading.BoundedSemaphore(1) for i in range(5)]41 sems2 = [threading.BoundedSemaphore(1) for i in range(5)]4243 for i in range(1,5):44 sems1[i]._value = 04546 for sem in sems2:47 sem._value = 04849 threads = [myThread(sems1,sems2,i,3) for i in range(5)]5051 for t in threads:52 t.start()53 ----------------------------------------------------54 Output:55 Thread 056 Thread 157 Thread 258 Thread 359 Thread 460 Thread 461 Thread 3

137

Page 138:  · 2020-03-17 · 13.8. Thediningphilosophers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93 13.9. Threadbarriers

62 Thread 263 Thread 164 Thread 065 Thread 066 Thread 167 Thread 268 Thread 369 Thread 470 Thread 471 Thread 372 Thread 273 Thread 174 Thread 075 Thread 076 Thread 177 Thread 278 Thread 379 Thread 480 Thread 481 Thread 382 Thread 283 Thread 184 Thread 0

The problem here can be seen in the last line of the output. We see that Thread 0 executed onetoo many times.

Exercise 14.29. Fix the thread pool code found in § 13.10

138