0

Given a simple generator with a return value:

def my_generator():
    yield 1
    yield 2
    return 3

I'm looking for a simple function which returns the generated list and the return value.

>>> output_and_return(my_generator())
([1, 2], 3)

There doesn't seem to be any clean way of doing this. Inside another generator, you can use value = yield from my_generator(), which will get you the return value, but won't directly give you the output list itself.

The closest I can come up with is wrapping it around an iterator which captures the return value:

class Generator:
    def __init__(self, gen):
        self.gen = gen

    def __iter__(self):
        self.value = yield from self.gen
    
    def output_and_return(self):
        return list(self), self.value

Generator(my_generator()).output_and_return()

Which works, but it's anything but simple or clean. Does anyone know a simpler way of extracting the list of values and also the return value of a generator without wrapping it inside another class?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Ehsan Kia
  • 1,475
  • 18
  • 26
  • 2
    You can't get both. A function is either a generator or has a normal return. Why don't you just Do The Right Thing and make that `return` into `yield` as well? – Tim Roberts Dec 17 '21 at 23:53
  • Actually since Python 3.3, `return ` in a generator is now equivalent to `raise StopIteration()`. See [PEP 380](https://www.python.org/dev/peps/pep-0380/) and [Return in generator together with yield in Python 3.3](https://stackoverflow.com/questions/16780002/return-in-generator-together-with-yield-in-python-3-3) – smci Dec 17 '21 at 23:56
  • 1
    I don't know why you'd call this an abuse of the language, it's not a great pattern but it's got some limited use cases. – CJR Dec 18 '21 at 00:01
  • 1
    All comments above saying this is an abuse of the language are blatantly wrong. This is very well part of the spec, look at PEP255 and the typing information https://docs.python.org/3/library/typing.html#typing.Generator – Ehsan Kia Dec 18 '21 at 00:02
  • I edited the self-correction to PEP 380 now allowing this since 3.3. Also [PEP 479](https://www.python.org/dev/peps/pep-0479/): `raise StopIteration` is on a deprecation schedule, [What is the difference between raise StopIteration and a return statement in generators?](https://stackoverflow.com/questions/14183803/what-is-the-difference-between-raise-stopiteration-and-a-return-statement-in-gen) – smci Dec 18 '21 at 00:08

2 Answers2

1

without wrapping it inside another class?

Maybe with just a function instead?

Version 1:

def output_and_return(it):
    def with_result():
        yield (yield from it)
    *elements, result = with_result()
    return elements, result

Version 2:

def output_and_return(it):
    result = None
    def get_result():
        nonlocal result
        result = yield from it
    return list(get_result()), result
Kelly Bundy
  • 23,480
  • 7
  • 29
  • 65
  • These solutions have the disadvantage that the results of the generator are collected in a variable. – auxsvr Mar 22 '22 at 20:35
  • @auxsvr Do you mean in a list? That's what the question explicitly asks for. – Kelly Bundy Mar 22 '22 at 21:04
  • You're right, I had something else in mind. – auxsvr Mar 22 '22 at 21:12
  • @auxsvr Though yes, the class is nice if someone doesn't want a list but just [iterate the iterable and then see the result value](https://tio.run/##XY/BCsMgDIbvPkVuUyiDrZdR2HmPIWWNTrBaUjvap3dau9Itp/DH70sclvDyrr4NFGOHCvpFanRIbfDERcMg1WLQdnA59Ne1JwwTOagZe9p2HOHxBQuWdVIaZ4KUfESrKkjqzZkrZ@cUwT0PjkxA2pj/5@/WTpiAcoci3@8axnQa7Efw368IpjzBDMaBLs6BjAv8hBZ7dKE5VTALtoWE42TXTJeVIsYP). – Kelly Bundy Mar 22 '22 at 21:19
  • I was trying to find a way to work around the limitation that one cannot `yield from` in a coroutine when I read your answer. – auxsvr Mar 22 '22 at 21:38
0

I guess we can extract it from the StopIteration exception like so:

def output_and_return(iterator):
  output = []
  try:
    while True:
      output.append(next(iterator))
  except StopIteration as e:
    return output, e.value

Not the cleanest code but quite a bit better than the class alternative.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Ehsan Kia
  • 1,475
  • 18
  • 26