My First Python 3 Program
2015-01-11
Today I wrote my first Python 3 program. I.e. my first program that runs under Python 3 but not under Python 2.
The (small...) task I wanted to solve was that I wanted to create a program that executed some shell stuff every fifth second until ctrl-c was pressed. I could have wrapped the logic code in a catch block and caught KeyError, but for some reason I wanted to use a signal handler to trap the SIGINT signal instead.
The first version of the program used a global variable to control the looping:
import signal
import time
interrupted = False
def signal_handler(signal, notused):
print "Signal handler called"
global interrupted
interrupted = True
signal.signal(signal.SIGINT, signal_handler)
while interrupted == False:
print "Still looping"
time.sleep(5)
Version 1 of the ctrl-c handling program. Uses a global variable.
As global variables are considered ugly, I then tried to come up with a version that replaces the global variable interrupted
with something else. Some googing seemed to indicate that using a closure might be a solutiion so I tried the following:
import signal
import time
def main_function():
interrupted = False
def signal_handler(signal, notused):
print "Signal handler called"
interrupted = True
signal.signal(signal.SIGINT, signal_handler)
while interrupted == False:
print "Still looping"
time.sleep(5)
if __name__ == '__main__':
main_function()
Version 2 of the ctrl-c handling program. Nonfunctional.
The global variable interrupted
has been replaced by a local variable (in the function main_function()
) with the same name. Not that much of an improvement, but still. At least global variables aren't used.
The problem with this program was that altough the signal handler was called when ctrl-c was pressed, the loop was not terminated:
lars@lars-lilla:~/text/blogg/150107$ python signal_handler_test_2.py
Still looping
Still looping
^CSignal handler called
Still looping
Still looping
Some more googling turned up the answer to why the program behaved like this. Even tough the signal handler is as a closure it cannot modify variables defined in the enclosing scope. It can read the variables, but now change them.
So how does Python 3 comes into the equation? It turns out that there is a new keyword nonlocal
in Python 3. nonlocal
lets a variable that is defined in an outer, but not global, scope be modified in an inner scope.
The third version of the program, the one that uses nonlocal
looks like this:
import signal
import time
def main_function():
interrupted = False
def signal_handler(signal, notused):
print("Signal handler called")
nonlocal interrupted
interrupted = True
signal.signal(signal.SIGINT, signal_handler)
while interrupted == False:
print("Still looping")
time.sleep(5)
if __name__ == '__main__':
main_function()
Version 3 of the ctrl-c handling program. Uses nonlocal
Note that as print
is a function instead of a statement in Python 3, I had to change print
to print()
.
Appart from that, the only thing that is changed compared to version 2 is the row nonlocal interrupted
.
When running this program, it works as expected:
lars@lars-lilla:~/text/blogg/150107$ python3 signal_handler_test_3.py
Still looping
Still looping
^CSignal handler called
lars@lars-lilla:~/text/blogg/150107$
Woohooo! Success! One has be able to take pride in even small victories ;)
In the end I rewrote the program to use a class instead. This way the interrupted
flag could be encapsulated even better. That program looked liked this:
import signal
import time
class SignalHandler(object):
def __init__(self):
self.interrupted = False
signal.signal(signal.SIGINT, self.signal_handler)
def signal_handler(self, signal, notused):
print("Signal handler called")
self.interrupted = True
def start(self):
while self.interrupted == False:
print("Still looping")
time.sleep(5)
if __name__ == '__main__':
instance = SignalHandler()
instance.start()
Version 4 of the ctrl-c handling program. Uses a class to encapsulate the logic.
So why didn't I use a class in the first place? Because it wasn't meant to live more than a few minutes. I just wanted to try somehing out and then throw away the program. Then one thing led to another, and after a while I'd written my first Python 3 program. Not very impressive, but still, it's a start ;)