Path: csiph.com!usenet.pasdenom.info!weretis.net!feeder4.news.weretis.net!rt.uk.eu.org!newsfeed.xs4all.nl!newsfeed2.news.xs4all.nl!xs4all!newsgate.cistron.nl!newsgate.news.xs4all.nl!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'else:': 0.03; 'argument': 0.05; 'true,': 0.05; '(using': 0.07; 'none,': 0.07; 'none:': 0.07; '*args,': 0.09; '22,': 0.09; 'arguments': 0.09; 'boring': 0.09; 'false,': 0.09; 'try:': 0.09; 'url:archive': 0.09; 'wrapper': 0.09; 'url:blog': 0.10; 'python': 0.11; 'def': 0.12; 'random': 0.14; 'times,': 0.14; 'args,': 0.16; 'callable': 0.16; 'callable.': 0.16; 'caller.': 0.16; 'caveat': 0.16; 'defer': 0.16; 'deferred': 0.16; 'evening,': 0.16; 'indexerror:': 0.16; 'substituted': 0.16; 'terribly': 0.16; 'twisted': 0.16; 'valueerror': 0.16; 'wraps': 0.16; 'wrote:': 0.18; 'thu,': 0.19; 'programming': 0.22; 'import': 0.22; 'aug': 0.22; 'number)': 0.24; 'looks': 0.24; 'sort': 0.25; "i've": 0.25; 'this:': 0.26; 'pass': 0.26; 'gets': 0.27; 'header:In-Reply-To:1': 0.27; 'function': 0.29; 'am,': 0.29; 'raise': 0.29; 'wonder': 0.29; 'message- id:@mail.gmail.com': 0.30; 'along': 0.30; "i'm": 0.30; 'code': 0.31; '(since': 0.31; "they'll": 0.31; 'class': 0.32; 'community': 0.33; 'running': 0.33; 'skip:d 20': 0.34; 'could': 0.34; 'except': 0.35; 'something': 0.35; 'but': 0.35; 'received:google.com': 0.35; 'there': 0.35; 'functions.': 0.36; "he's": 0.36; 'done': 0.36; 'similar': 0.36; 'url:org': 0.36; 'expected': 0.38; 'to:addr :python-list': 0.38; 'anything': 0.39; 'to:addr:python.org': 0.39; 'called': 0.40; 'days': 0.60; 'son': 0.61; 'course': 0.61; 'myself': 0.63; 'side': 0.67; 'promise': 0.68; 'results': 0.69; 'await': 0.74; 'result))': 0.84; 'results,': 0.84; 'url:2013': 0.84; 'numbers:': 0.91; 'have.': 0.93; '2013': 0.98 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :content-type; bh=8P3/76ENTJ+Jo4MAnSrvu5dN+JOvkhfhFol3fcrNXDI=; b=RjotPNM8/7faMpj5lXHaqYcCm3URSGDtMHKVB/iILBnkO3g9yqKsPtJHKy0fp9T2e5 c1MYyWiP6rvnRN8ZNyBlw/pJ9HJ0UdIDqedD8WJvz4+PjmB1lNqdFRifN7B5hrlvFhLE ERgO6QCU878UaTxUC7SJvQCkjG3xP+Dq9F7TKeE4YQ2wJMftqmVqX3wP6QKewzKi5iGT C6yc6SYBychbLTWfIL4XDvNfdAl66VoY39SFIqFTGcG2aD3WQP5la68MB3qYAD7Dsund fKVB8PIYmokMc8zG8axt1msvtCPGKYCPgFLALyPxQGxQWAx+4wYC/ME8KX9eEjtqucCS BgTA== X-Received: by 10.52.180.229 with SMTP id dr5mr12181892vdc.20.1377231119228; Thu, 22 Aug 2013 21:11:59 -0700 (PDT) MIME-Version: 1.0 In-Reply-To: References: From: Ian Kelly Date: Thu, 22 Aug 2013 22:11:19 -0600 Subject: Re: c# async, await To: Python Content-Type: text/plain; charset=ISO-8859-1 X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 125 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1377231127 news.xs4all.nl 15883 [2001:888:2000:d::a6]:44356 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:52862 On Thu, Aug 22, 2013 at 5:29 AM, Neal Becker wrote: > So my son is now spending his days on c# and .net. He's enthusiastic about > async and await, and said to me last evening, "I don't think python has anything > like that". I'm not terribly knowledgeable myself regarding async programming > (since I never need to use it). I did look at this: > > http://tirania.org/blog/archive/2013/Aug-15.html > > I wonder what response the python community might have. I've done something sort of similar to await in Python using restartable functions. The code looks like this (using Twisted Deferreds, but any sort of promise could be substituted in): from functools import wraps from twisted.internet import defer def restartable(func): def resume(result, is_failure, results, args, kws): def await(get_deferred, *args, **kws): try: is_failure, result = reversed_results.pop() except IndexError: raise Await(get_deferred(*args, **kws)) if is_failure: result.raiseException() return result def do_once(func, *args, **kws): return await(defer.maybeDeferred, func, *args, **kws) await.do_once = do_once if results is None: results = [] else: results.append((is_failure, result)) reversed_results = list(reversed(results)) try: func(await, *args, **kws) except Await as exc: deferred = exc.args[0] deferred.addCallback(resume, False, results, args, kws) deferred.addErrback(resume, True, results, args, kws) @wraps(func) def wrapper(*args, **kws): return resume(None, None, None, args, kws) return wrapper class Await(BaseException): pass The usage of restartable and await then looks something like this: @restartable def random_sum(await): try: a = await(random_number) b = await(random_number) c = await(random_number) d = await(random_number) except ValueError as exc: print("Couldn't get four numbers: " + exc.message) return print('{} + {} + {} + {} = {}'.format(a, b, c, d, a + b + c + d)) The "await" argument is passed in by the restartable machinery, not by the caller. The argument passed to await is a callable that is expected to return a Deferred, and any additional arguments are passed along to the callable. A boring implementation of the "random_number" callable might look like this: def random_number(): from random import randrange from twisted.internet import defer, reactor deferred = defer.Deferred() if randrange(4) > 0: number = randrange(42) print("Generated {}".format(number)) reactor.callLater(1, deferred.callback, number) else: print("Failed") reactor.callLater(1, deferred.errback, ValueError("Not available")) return deferred Of course the big caveat to all this is that since the function is restartable, the "random_sum" function above actually gets called five times, and so if there are any side effects before the last await, they'll end up happening multiple times. This can be averted using await.do_once: @restartable def random_sum(await): try: await.do_once(print, 1) a = await(random_number) await.do_once(print, 2) b = await(random_number) await.do_once(print, 3) c = await(random_number) await.do_once(print, 4) d = await(random_number) except ValueError as exc: print("Couldn't get four numbers: " + exc.message) return print('{} + {} + {} + {} = {}'.format(a, b, c, d, a + b + c + d)) The result of running this is: 1 Generated 35 2 Generated 28 3 Generated 32 4 Generated 16 35 + 28 + 32 + 16 = 111