Source

pyfutures / pyfutures.py

Full commit
"""Implements basic futures in Python as a decorator. If a function is decorated
   as a future, it will instead execute in a separate thread of execution and
   return a wrapped_future_call instance. You can query your future.value for a
   blocking return value; if the function call raised an exception it will be
   re-raised when this is accessed. For a non-blocking query, you can check the
   future.done property or check future.nonblocking_value, which returns None
   by default."""

try:
    import functools
except: # Support older versions that don't have functools
    functools = None

import threading

class future(object):
    class wrapped_future_call(threading.Thread):
        def __init__(self, f, a, kw):
            self._f, self._a, self._kw, self._l = f, a, kw, threading.Lock()
            threading.Thread.__init__(self)
            self.setDaemon(False)
            self.start()
        def run(self):
            try:
                _return = self._f(*self._a, **self._kw)
                self._l.acquire()
                self._return = _return
                self._done = True
                self._l.release()
            except Exception, e:
                self._l.acquire()
                self._exception = e
                self._l.release()
        @property
        def done(self):
            self._l.acquire()
            rv = getattr(self, '_done', False)
            self._l.release()
            return rv
        @property
        def value(self):
            if not self.done:
                self.join()
            self._done = True
            if hasattr(self, '_return'):
                ret = self._return
                return ret
            elif hasattr(self, '_exception'):
                raise self._exception
        @property
        def nonblocking_value(self, default=None):
            if not self.done:
                return default
            return self.value
    def __init__(self, fn):
        self._fn = fn
        if functools:
            functools.update_wrapper(self, fn)
    def __call__(self, *a, **kw):
        return self.wrapped_future_call(self._fn, a, kw)

if __name__ == "__main__":
    import time, random
    @future
    def sleepyprint(delay, numbers, name='call'):
        import time
        for x in range(numbers):
            print "%s [0..%i]@%i, %f" % (name, numbers-1, x, delay)
            time.sleep(delay)
        return "%03.4f secs." % (float(numbers+1) * delay)
    prints = [sleepyprint(random.random()/2.0, random.randint(1, 10), 'call-%i'%g) for g in range(10)]
    bold = "\033[1;31m*\033[1;34m%s\033[1;31m*\033[0m"
    for i in range(20):
        time.sleep(0.125)
        print ", ".join([repr(y.nonblocking_value) for y in prints])
    time.sleep(0.25)
    print " ".join([bold % y.value for y in prints])