Coroutines and argument tupling
Marshall T. Vandegrift
llasram at gmail.com
Thu Aug 16 07:37:07 EDT 2007
Bjoern Schliessmann <usenet-mail-0306.20.chr0n0ss at spamgourmet.com> writes:
> The solution I'd use is a decorator that calls next automatically one
> time after instantiation. Then you can use send normally, and don't
> have to care about any initial parameters, which makes the code
> clearer (initial parameters should be used for setup purposes, but not
> for the first iteration, IMHO). It'd look like this (from PEP 342,
> http://www.python.org/dev/peps/pep-0342/):
I'd seen the consumer decorator, and it certainly is cleaner than just
using a generator. I don't like how it hides the parameter signature in
the middle of the consumer function though, and it also doesn't provide
for argument default values. It's the difference between:
...
def __init__(self, ...):
...
self.consumer = self._function(value)
...
def function(self, first, second=3, *args, **kwargs):
self.consumer.send((first, second, args, kwargs))
@consumer
def _function(self, setup):
...
first, second, args, kwargs = yield # initial 'next'
while condition:
...
first, second, args, kwargs = yield retval
Versus just:
@coroutine
def function(self, first, second=3, *args, **kwargs):
...
while condition:
...
first, second, args, kwargs = yield retval
Thanks in any case for the replies! Since I've apparently decided my
ArgPacker is worth it, the complete code for my coroutine decorator
follows.
-Marshall
import inspect
import types
import functools
from itertools import izip
__all__ = [ 'coroutine' ]
class ArgPacker(object):
def __init__(self, function):
args, varargs, varkw, defaults = inspect.getargspec(function)
self.args = args or []
self.varargs = (varargs is not None) and 1 or 0
self.varkw = (varkw is not None) and 1 or 0
self.nargs = len(self.args) + self.varargs + self.varkw
defaults = defaults or []
defargs = self.args[len(self.args) - len(defaults):]
self.defaults = dict([(k, v) for k, v in izip(defargs, defaults)])
def pack(self, *args, **kwargs):
args = list(args)
result = [None] * self.nargs
for i, arg in izip(xrange(len(self.args)), self.args):
if args:
result[i] = args.pop(0)
elif arg in kwargs:
result[i] = kwargs[arg]
del kwargs[arg]
elif arg in self.defaults:
result[i] = self.defaults[arg]
else:
return None
if self.varargs:
result[len(self.args)] = args
elif args:
return None
if self.varkw:
result[-1] = kwargs
elif kwargs:
return None
return tuple(result)
class coroutine(object):
"""Convert a function to be a simple coroutine.
A simple coroutine is a generator bound to act as much as possible like a
normal function. Callers call the function as usual while the coroutine
produces new return values and receives new arguments with `yield'.
"""
def __init__(self, function):
self.function = function
self.gname = ''.join(['__', function.__name__, '_generator'])
self.packer = ArgPacker(function)
coroutine = self
def method(self, *args, **kwargs):
return coroutine.generate(self, self, *args, **kwargs)
self.method = method
functools.update_wrapper(self, function)
functools.update_wrapper(method, function)
def __get__(self, obj, objtype=None):
return types.MethodType(self.method, obj, objtype)
def __call__(self, *args, **kwargs):
return self.generate(self, *args, **kwargs)
def generate(self, obj, *args, **kwargs):
try:
generator = getattr(obj, self.gname)
except AttributeError:
generator = self.function(*args, **kwargs)
setattr(obj, self.gname, generator)
retval = generator.next()
else:
packed = self.packer.pack(*args, **kwargs)
if packed is None:
self.function(*args, **kwargs) # Should raise TypeError
raise RuntimeError("ArgPacker reported spurious error")
retval = generator.send(packed)
return retval
More information about the Python-list
mailing list