[x^2 for x in lst]

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 ;)


Leave a reply

Your name as it will be displayed when the comment is posted on the page. Your email address will not be published.