5

Is it possible, and if so, advisable, and if so, what would be the recommended method for decorating a function that yields a value?

For example, consider this imaginary example I made up

def foobar_creator(func):
    def wrapped(**kwargs):
       res = func(**kwargs)
       flag = True
       for k,v in kwargs:
           if res % v == 0:
               flag = False
               yield k
       if flag: 
            yield res
     return wrapped

@foobar_creator
def generic_yielder(**kwargs):
     for i in xrange(sys.maxint):
           yield i

for i in generic_yielder(foo=3, bar=5, foobar=15):
      print i
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
yayu
  • 7,758
  • 17
  • 54
  • 86
  • 1
    Looks overcomplicated. What specifically do you mean by "decorating a function that yields a value"? What use case do you have in mind? – ivan_pozdeev Nov 23 '14 at 10:35
  • @ivan_pozdeev use case: i have a bunch of generator functions that yield new values from an api. I want to apply filters without touching the existing function. So for a stripped down exmaple example `def get_new(): result = new_result(); yield result` I want to filter out and yield only, say results which contain the string 'foobar' without mucking the complicated `new_result` processes – yayu Nov 23 '14 at 11:03

5 Answers5

6

A generator function, when called, returns an iterator object. If your decorator is itself a generator too, you'll need to loop over the wrapped result:

def foobar_creator(func):
    def wrapped(**kwargs):
        gen = func(**kwargs)
        flag = True
        for k, v in kwargs:
            if res % v == 0:
                flag = False
                yield k
        if flag:
            for res in gen:
                yield res
    return wrapped

If you are using Python 3.3 or up, you can use delegation to hand control the wrapped generator, by using yield from:

if flag:
    yield from gen
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
2

Instead of yielding every potential return value, why not yield only those that actually exist? Something like

def wrap(f, arg):
    for x in f(arg):
        yield x

(actual decorator syntax, handling of positional and keyword arguments, etc. is omitted for clarity.)

2

For the case in comment42684128, the solution is as simple as:

(v for v in f(<args>) if filter_condition(v))

As a decorator:

def yfilter(filter_condition):
   def yfilter_p(f):
       def wrapped(*args,**kwargs):
           return (v for v in f(*args,**kwargs) if filter_condition(v))
       return wrapped
   return yfilter_p
Community
  • 1
  • 1
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
0

The existing answers don't handle generators that yield and then return a value. For that, you need to return (yield from f()):

def dec(f):
    def g():
        return (yield from f())
    return g

@dec
def f():
   yield 'val'
   return 'done'
Andy Jones
  • 4,723
  • 2
  • 19
  • 24
-1

If you want to decorate using type hints, do it like this

from typing import Generator

def generate() -> Generator[int, None, None]:
    for i in range(10):
        yield i

https://stackoverflow.com/a/42531262/1812732

John Henckel
  • 10,274
  • 3
  • 79
  • 79