[Python-ideas] Possible PEP 380 tweak

Jacob Holm jh at improva.dk
Thu Oct 28 12:12:55 CEST 2010


On 2010-10-27 18:53, Jacob Holm wrote:
> Hmm.  This got me thinking.  One thing I'd really like to see in python
> is something like the "channel" object from the go language
> (http://golang.org/).
> 
> Based on PEP 380 or Gregs new cofunctions PEP (or perhaps even without
> any of them) it is possible to write a trampoline-based implementation
> of a channel object with "send" and "next" methods that work as
> expected.  One thing that is *not* possible (I think) is to make that
> object iterable.  Your wild idea above gave me a similar wild idea of my
> own.  An extension to the cofunctions PEP that would make that possible.
> 

Seems like I screwed up the semantics of the standard for-loop in that
version.  Let me try again...

1) Add new exception StopCoIteration, inheriting from StandardError.
Change the regular StopIteration to inherit from the new exception
instead of directly from StandardError.  This ensures that code that
catches StopCoIteration also catches StopIteration, which I think is
what we want.

The new exception is needed because "cocall func()" can never raise the
regular StopIteration (or any subclass thereof).  This might actually be
an argument for using a different exception for returning a value from a
coroutine...

2) Allow __next__ on an object to be a cofunction.  Add a __cocall__ to
the built-in next(ob) that tries to uses cocall to call ob.__next__.

def next__cocall__(ob, *args):
    if len(args)>1:
        raise TypeError
    try:
        _next = type(ob).__next__
    except AttributeError:
        raise TypeError
    try:
        return cocall _next(ob)
    except StopCoIteration:
        if args:
           return args[0]
        raise

2a) Optionally allow __iter__ on an object to be a cofunction.  Add a
__cocall__ to the builtin iter.

   class _func_iter(object):
       def __init__(self, callable, sentinel):
           self.callable = callable
           self.sentinel = sentinel
       def __next__(self):
           v = cocall self.callable()
           if v is sentinel:
               raise StopCoIteration
           return v

   def iter__cocall__(*args):
       try:
           ob, = args
       except ValueError:
           try:
               callable, sentinel = args
           except ValueError:
               raise TypeError
           return _func_iter(callable, sentinel)
       try:
           _iter = type(ob).__iter__
       except AttributeError:
           raise TypeError
       return cocall _iter(ob)

3) Change the for-loop in a cofunction:

   for val in iterable:
       <block>
   else:
       <block>

so it expands into:

   _it = cocall iter(iterable)
   while True:
       try:
           val = cocall next(iterable)
       except StopCoIteration:
           break
       <block>
   else:
       <block>

which is exactly the normal expansion, but using cocall to call iter and
next, and catching StopCoIteration instead of StopIteration.

Since cocall falls back to using a regular call, this should work well
with all normal iterables.

3a)  Alternatively define a new syntax for "coiterating", e.g.

    cocall for val in iterable:
        <block>
    else:
        <block>



All this to make it possible to write a code like this:


def consumer(ch):
    cocall for val in ch:
        print(val)

def producer(ch):
    cocall for val in range(10):
        cocall ch.send(val)

def main()
    sched = scheduler()
    ch = channel()
    sched.add(consumer(ch))
    sched.add(producer(ch))
    sched.run()


Thoughts?

- Jacob



More information about the Python-ideas mailing list