Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Introduction to Python
Lecture 8
Summary
• Python exceptions
• Processes and Threads
• Programming with Threads
Python Exceptions
In Python, there are two distinguishable kinds of errors: syntax errors and
exceptions.
Exceptions are events that can modify the flow of control through a program.
• Error handling
• Event notification
• Termination actions
try/except
Catch and recover from exceptions raised by Python, or by you.
try/finally
Perform cleanup actions, whether exceptions occur or not.
raise
Trigger an exception manually in your code.
assert
Conditionally trigger an exception in your code.
with/as
Implement context managers in Python 2.6 and 3.0 (optional in 2.5)
The general format of the try statement
try:
<statements> # Run this main action first
except <name1>:
<statements> # Run if name1 is raised during try block
except (name2, name3):
<statements> # Run if any of these exceptions occur
except <name4> as <data>:
<statements> # Run if name4 is raised, and get instance raised
except:
<statements> # Run for all (other) exceptions raised
else:
<statements> # Run if no exception was raised during try block
try statement clauses
Clause Comments
except name as value: Catch the listed exception and its instance
except (name1, name2) as value: Catch any listed exception and its instance
The recommended mode for creating custom exceptions is by using OOP, using
classes and classes hierarchies.
• They can be organized in categories (adding future categories will not require
changes in the try block)
Let’s suppose we need to deal with a file, open it, do some processing, then close it
This is extremely useful when we involve various resources in our program and we
have to release them regardless of what events could occur during the execution of
the program flow
Unified try/except/finally
In this case, the exception raised in process_file() function is caught and processed in
the exception handler defined in the except block, then the code defined in finally
section is executed. In this way we assure ourselves that we release all the resources that
we used during the program (file handlers, connections to databases, sockets, etc…)
C vs. Python error handling
Every process has at least one thread, i.e. the process itself. A process
can start multiple threads. The operating system executes these
threads like parallel "processes". On a single processor machine, this
parallelism is achieved by thread scheduling or timeslicing.
Advantages of Threading:
Two scenarios:
Thread-Specific State
_thread.start_new_thread method was used here to start a new thread. One can notice
here that we have a while loop in the main program, looping forever. This is to avoid the
situation in which the main program starts the three threads, but it exits before the threads
finish their execution, and the buffers are not flushed (the output is buffered by default)
See the program output and notice that the threads output is displayed after the main
program end its execution.
First threading program
import threading
We want to increment the variable
count from two distinct threads.
count = 0
For this, we define a function that
def increment_count(times):
increments that variable and pass
global count
for i in range(1, times):
it to the Thread constructor.
count += 1
return count
But, wait! What happens with the
output? We expect 2000000 but
if __name__ == "__main__":
every time the result is different.
t1 = threading.Thread(target=increment_count, args=[1000001])
t2 = threading.Thread(target=increment_count, args=[1000001])
Not really what we wanted, right?
t1.start()
t2.start()
t1.join()
t2.join()
Output:
print(count)
1285489
Process finished with exit code 0
import threading
This time we add a lock around
count = 0
the critical section represented by
def increment_count(times):
the counter increment
global count
for i in range(1, times):
lock.acquire()
count += 1
lock.release()
return count
This time, the result is always the
same and the correct one.
if __name__ == "__main__":
lock = threading.Lock()
t1 = threading.Thread(target=increment_count, args=[1000001])
t2 = threading.Thread(target=increment_count, args=[1000001])
t1.start()
t2.start()
t1.join()
Output:
t2.join()
print(count)
2000000
Process finished with exit code 0
2000000
Process finished with exit code 0
2000000
Process finished with exit code 0
Synchronizing threads
import threading
import time
We want to print some info about
the running threads, first the info
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
about one thread, then the info
threading.Thread.__init__(self)
about a second thread.
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("Starting " + self.name)
print_time(self.name, self.counter, 3)
But, wait! What happens with the
output? The print result is
def print_time(threadName, delay, counter):
while counter:
scrambled between threads…
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
Not really what we wanted, right?
threads = []
# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
Output:
# Start new Threads
thread1.start()
Starting Thread-1
thread2.start()
Starting Thread-2
# Add threads to thread list
Thread-1: Thu Nov 24 18:20:49 2016
threads.append(thread1)
Thread-2: Thu Nov 24 18:20:50 2016
threads.append(thread2)
Thread-1: Thu Nov 24 18:20:50 2016
# Wait for all threads to complete
Thread-1: Thu Nov 24 18:20:51 2016
for t in threads:
Thread-2: Thu Nov 24 18:20:52 2016
t.join()
print ("Exiting Main Thread") Thread-2: Thu Nov 24 18:20:54 2016
Exiting Main Thread
Synchronizing threads
import threading
import time
This time one can see that the
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
output looks much better.
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("Starting " + self.name)
# Get lock to synchronize threads
threadLock.acquire()
print_time(self.name, self.counter, 3)
# Free lock to release next thread
critical section
threadLock.release()
def print_time(threadName, delay, counter):
while counter:
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
threadLock = threading.Lock()
threads = []
# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
Output:
# Start new Threads
thread1.start()
Starting Thread-1
thread2.start()
Starting Thread-2
# Add threads to thread list
Thread-1: Thu Nov 24 18:26:59 2016
threads.append(thread1)
Thread-1: Thu Nov 24 18:27:00 2016
threads.append(thread2)
Thread-1: Thu Nov 24 18:27:01 2016
# Wait for all threads to complete
Thread-2: Thu Nov 24 18:27:03 2016
for t in threads:
Thread-2: Thu Nov 24 18:27:05 2016
t.join()
Thread-2: Thu Nov 24 18:27:07 2016
print ("Exiting Main Thread")
Exiting Main Thread
Queue data structure
• get(): The get() removes and returns an item from the queue.
• qsize() : The qsize() returns the number of items that are currently in the
queue.
Starting Thread-1
Starting Thread-2
Starting Thread-3
Thread-1 processing One
Thread-2 processing Two
Thread-3 processing Three
Thread-1 processing Four
Thread-3 processing Five
Exiting Thread-1
Exiting Thread-2
Exiting Thread-3
Exiting Main Thread
References