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()