How to add a timeout to a function in Python

The principal problem with your code is the overuse of the double underscore namespace conflict prevention in a class that isn’t intended to be subclassed at all.

In general, self.__foo is a code smell that should be accompanied by a comment along the lines of # This is a mixin and we don't want arbitrary subclasses to have a namespace conflict.

Further the client API of this method would look like this:

def mymethod(): pass

mymethod = add_timeout(mymethod, 15)

# start the processing    
timeout_obj = mymethod()
try:
    # access the property, which is really a function call
    ret = timeout_obj.value
except TimeoutError:
    # handle a timeout here
    ret = None

This is not very pythonic at all and a better client api would be:

@timeout(15)
def mymethod(): pass

try:
    my_method()
except TimeoutError:
    pass

You are using @property in your class for something that is a state mutating accessor, this is not a good idea. For instance, what would happen when .value is accessed twice? It looks like it would fail because queue.get() would return trash because the queue is already empty.

Remove @property entirely. Don’t use it in this context, it’s not suitable for your use-case. Make call block when called and return the value or raise the exception itself. If you really must have value accessed later, make it a method like .get() or .value().

This code for the _target should be rewritten a little:

def _target(queue, function, *args, **kwargs):
    try:
        queue.put((True, function(*args, **kwargs)))
    except:
        queue.put((False, exc_info())) # get *all* the exec info, don't do exc_info[1]

# then later:
    raise exc_info[0], exc_info[1], exc_info[2]

That way the stack trace will be preserved correctly and visible to the programmer.

I think you’ve made a reasonable first crack at writing a useful library, I like the usage of the processing module to achieve the goals.

Leave a Comment

tech