I started using python 3 over python 2 for the last three years, and I think you should do it too if you have not done yet. First python 2.7 end-of-life date is in 2020, and there will not be a python 2.81. Second, python 3 syntax improvements and new APIs are so much more helpful and practical, that so far it has to be worth the effort of learning python 3. One example that comes to my mind is when implementing timeouts when making a system call or when calling a function within python interpreter.

Let’s start first with the case of a system call. An example of how to implement a system call protected by a timeout in python 2 can be found below.

import exceptions
import subprocess
import sys
import time

class TimeoutError(Exception):
    pass

def call(command, timeout=5, polltime=0.1):
    proc = None
    stdout = ''
    stderr = ''
    try:
        proc = subprocess.Popen(
            command,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            close_fds=True
        )
        deadline = time.time() + timeout
        while time.time() < deadline and proc.poll() == None:
            time.sleep(polltime)
        if proc.poll() == None:
            proc.kill()
            raise TimeoutError('Command timeout')
        else:
            if proc.returncode:
                 raise subprocess.CalledProcessError(proc.returncode, command)
            stdout, stderr = proc.communicate()
    except:
        if proc.poll() == None:
            proc.kill();
        raise
    return stdout, stderr

stdout, stderr = call('echo works!')
print stdout, stderr

The implementation of this example plus some more comments about its implementation can be found in this code at my exercise repo.

The equivalent example in python 3 is much simpler, and it is shown below.

from subprocess import PIPE, run

proc = run('echo works', shell=True, timeout=5, stdout=PIPE, stderr=PIPE)
print (proc.stdout.decode('utf-8'), proc.stderr.decode('utf-8'))

As you can see, in python 3, the timeout is now a native functionality of the new API. Now, you might think this is not such a big deal. However, inappropriate timeout implementation opens the possibility of leaving dangling subprocesses in some edge cases when exceptions are raised. If your program runs for a long time, this could leak processes used when for executing each command, eventually depleting host resources.

Another nontrivial implementation is adding a timeout for a function called within the python interpreter. This issue created several discussions in different forums2, and it was also the inspiration for small timeout library3. However, using new python 3 concurrent.futures API, it is possible to create a simple function timeout because the timeout functionality is again a native functionality of the API.

For example, if you would like to make a timeout decorator, you can write something like it is shown below.

from concurrent.futures import ThreadPoolExecutor
from functools import wraps
import time

def ftimeout(timeout):
    """Implement function timeout decorator."""
    def decorator(callback):
        @wraps(callback)
        def wrapper(*args, **kargs):
            with ThreadPoolExecutor(max_workers=1) as executor:
                future = executor.submit(callback, *args, **kargs)
                return future.result(timeout=timeout)
        return wrapper
    return decorator

Now, you can now add a timeout by simply decorating the function of interest:

@ftimeout(timeout=2)
def sleep(secs):
    time.sleep(secs)
# it will timeout before return
sleep(secs=4)

for fully functional example can be found in my example repo.

It is also possible to decorate member functions with a timeout that collects the waiting time from the object itself. Take a look at this decorator below.

from concurrent.futures import ThreadPoolExecutor
from functools import wraps
import time

def mtimeout(attribute):
    """Implement function member timeout decorator."""
    def decorator(callback):
        @wraps(callback)
        def wrapper(self, *args, **kargs):
            timeout = getattr(self, attribute)
            with ThreadPoolExecutor(max_workers=1) as executor:
                future = executor.submit(callback, self, *args, **kargs)
                return future.result(timeout=timeout)
        return wrapper
    return decorator

You can use this in the following way:

class ObjectType:
    def __init__(self, timeout):
        self._timeout = timeout
    @mtimeout(attribute='_timeout')
    def sleep(self, secs):
        time.sleep(secs)

o = ObjectType(timeout=2)
# it will timeout before return
o.sleep(4)

For fully functional example can be found in my example repo.

So there you have it, a few examples of improved python 3 API that I found particularly useful. In the era of distributed applications and microservices, it is fundamental to protect your code from unexpected behavior using timeouts.

References