A decorator that creates a generator factory that creates generators that can have attributes set on them (like a normal python class).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | from inspect import getargspec, formatargspec
_generator_with_attributes_redefinition = """
def decorated%(oldargs)s:
wrapper = type('GeneratorWithAttributes', (object,),
{"__iter__":lambda self: wrapped})()
wrapped = genfunc%(newargs)s
for x in ("close", "next", "send", "throw", "gi_code", "gi_frame",
"gi_running"):
setattr(wrapper, x, getattr(wrapped, x))
return wrapper
"""
def generator_with_attributes(genfunc):
"""Decorator to enable class-like attributes on a generator.
`genfunc` should take `self` for its first parameter.
Example:
>>> def f(self, x):
... while True:
... print self
... print dir(self)
... yield x
>>> dec = generator_with_attributes(f)
>>> g = dec(1234)
>>> g.next() # doctest: +ELLIPSIS
<GeneratorWithAttributes object at 0x...>
[...]
1234
>>> g.x = 123
>>> g.next() # doctest: +ELLIPSIS
<GeneratorWithAttributes object at 0x...>
[...'x'...]
1234
"""
old = getargspec(genfunc)
old[0].pop(0)
new = getargspec(genfunc)
new[0][0] = 'wrapper'
specs = {'name': genfunc.func_name, 'oldargs': formatargspec(*old),
'newargs': formatargspec(*new)}
exec _generator_with_attributes_redefinition % specs in locals()
decorated.__name__ = genfunc.__name__
decorated.__doc__ = genfunc.__doc__
return decorated
if __name__ == "__main__":
import doctest
doctest.testmod()
|
Based on Recipe 164044. It now supports coroutines and other new features of generators. Also I made __iter__ part of the actual wrapper object and not object.__dict__, because it wouldn't work any other way for me (Python 2.6.4).
Tags: concurrency, state