-3

I would like you to consider the following code:

def func(alist):
    if len(alist) == 1:
        return arg * 2
    for item in alist:
        yield item * 2

When I run it, I get this error:

SyntaxError: 'return' with argument inside generator

Now, I realize that I cannot do this. However, I would like to know why. What exactly is going on behind the scenes that is causing Python to throw the SyntaxError?

wim
  • 338,267
  • 99
  • 616
  • 750
  • I'm curious as to why you would want to sometimes yield and sometimes return. How would you use such a function properly? Wouldn't you need to inspect the length of the list you pass it beforehand, to make sure you call it the right way? – Kevin Aug 13 '13 at 17:56
  • possible duplicate of [Why doesn't the Python interpreter implicitly create the generator?](http://stackoverflow.com/questions/17825357/why-doesnt-the-python-interpreter-implicitly-create-the-generator) – agf Aug 13 '13 at 17:56
  • 1
    Rather than what you're trying, it would almost certainly be a better idea to always `yield` the results. – user2357112 Aug 13 '13 at 17:57
  • no yields do not have defined length and can be infinite; there is no way to know which one you want based on usual python paradigms – Eiyrioü von Kauyf Aug 13 '13 at 17:57
  • @Kevin - Because the list can be either 1 item exactly or many, many items. If it is only one, then using `return` is simple and good. Otherwise, I wanted to make a generator in order to return the items one at a time (thus cutting down on CPU usage). However, the main point of my question is "Why can't this be done?" –  Aug 13 '13 at 18:06
  • "_If it is only one, then using return is simple and good_". Can you explain how it would be simpler and better than using `yield`? – Kevin Aug 13 '13 at 18:07
  • 1
    Because I originally didn't really see a reason to create a generator for only 1 item. However, I've kinda come to the conclusion that that is what I should do. Like I said before though, the main point of my question was "Why can't I?" not "Should I?". I am curious as to how Python works internally and why it won't let it. –  Aug 13 '13 at 18:11
  • 1
    Since Python 3.3 you can. See https://www.python.org/dev/peps/pep-0380/ – wim Aug 03 '18 at 00:22

5 Answers5

8

Python has to decide whether a function is a generator at bytecode compilation time. This is because the semantics of generators say that none of the code in a generator function runs before the first next call; the generator function returns a generator iterator that, when next is called, runs the generator code. Thus, Python can't decide whether a function should be a generator or not by running it until it hits a yield or a return; instead, the presence of a yield in a function signals that the function is a generator.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 2
    I accept your answer because you answered what I wanted: "Why can't this be done?". The other answers focused more on finding a way around it (which is nice, but not what I wanted exactly). Thanks. –  Aug 14 '13 at 14:15
0

See here: What does the "yield" keyword do in Python?

This describes your issue in detail. In short, because the way Python will interprate the function (either as an generating object or a returning object, everything in Python is objects and some things will define that object as a specific type. In this case Yield is a generating object)

TL;DR: You can't use yield and return within the same function. Use either one as many times as you wan't within the function, just not both.

Community
  • 1
  • 1
Torxed
  • 22,866
  • 14
  • 82
  • 131
0

Is not that, you cannot use a return with yield, but rather, you cannot use return with argument, when your function has a yield making it a generator

You might want to change your implementation to

def func(lis):
    for item in lis:
        yield item * 2
Abhijit
  • 62,056
  • 18
  • 131
  • 204
  • May as well just delete the `if` entirely and do `def func(lis): for item in lis: yield item * 2` – Kevin Aug 13 '13 at 17:53
-1

What you want is indeed possible:

def func(lis):
    if len(lis) == 1:
        return lis[0] * 2
    else:
        return (item * 2 for item in lis)

This returns a value in the one case and a generator in the other case.

But be aware that this will lead to problems: On every call, you will have to make the distinction

res = func(lis)
if len(lis) == 1:
    print res # my value
else:
    for i in res: print i # several values

It is much more advisable to make the two use cases distinct:

  1. You can do

    def func(val):
        return val * 2
    

    and, when you need, do either of

    reslist = [func(i) for i in lis]
    resgen = (func(i) for i in lis)
    

    or

  2. you can do

    def func(lis):
        for item in lis:
            yield lis[0] * 2
    
    def func1(val):
        for i in func([val]):
            return i # returns the first value
    
glglgl
  • 89,107
  • 13
  • 149
  • 217
-1

The other answers already explained the yield/return issue. You can just use map to make this much simpler:

def func(lis):
    return map(lambda item: item * 2, lis)
   # similar to: return [item * 2 for item in lis]

Of course, this will always return a list, because lis is a list, even if it only has one item. You can change it to a compound statement:

def func(lis):
    return map(lambda item: item * 2, lis) if len(lis) > 1 else lis[0] * 2
Rushy Panchal
  • 16,979
  • 16
  • 61
  • 94