0

I'm confused by the following in Python 3.3:

>>> def foo(gen=False):
...     if not gen:
...         return list(range(10))
...     else:
...         for i in range(10):
...             yield i
... 
>>> foo()
<generator object foo at 0xb72a016c>
>>> foo(gen=False)
<generator object foo at 0xb72a0144>
>>> foo(gen=True)
<generator object foo at 0xb72a089c>
>>> 

What am I misunderstanding? If gen is False, the default value, then not gen is True, and thus I should get a list of integers [0,1,2,3,4,5,6,7,8,9]. On the other hand, if it is True, shouldn't (not gen) == False result in a generator?

bjd2385
  • 2,013
  • 4
  • 26
  • 47
  • Because python will always give you a generator if you have `yield` anywhere in your function, even if it is never reached – inspectorG4dget Oct 06 '16 at 04:22
  • @inspectorG4dget Wow I didn't know that. Now this seems like a silly question, but I've never read that _anywhere_. – bjd2385 Oct 06 '16 at 04:23
  • 3
    Possible duplicate of [Return value in generator function Python 2](http://stackoverflow.com/questions/39734786/return-value-in-generator-function-python-2) – Łukasz Rogalski Oct 06 '16 at 09:39

2 Answers2

3

Inclusion of yield in a function makes it into an generator function: When you execute the function, you get a generator; no other execution takes place. The function itself starts getting executed only when the generator starts being asked for elements.

def not_a_generator():
    print(1)
    print(2)

not_a_generator()
# => 1
#    2

def is_a_generator():
    print(1)
    yield 7
    print(2)

is_a_generator()
# => <generator object bar at 0x10e1471a8>

list(is_a_generator())
# => 1
#    2
#    [7]
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • I understand how this all works mechanically as you've written it, I just didn't realize you couldn't do it in the way I wrote it in my answer above (i.e. by @inspectorG4dget's comment, it doesn't matter where this keyword is placed in the control flow etc. it's automatically a generator function P:). Nevertheless, I appreciate your answer :) – bjd2385 Oct 06 '16 at 04:27
1

It does not matter that you have put the yield statement in an if branch. The documentation says:

Using yield in a function definition is sufficient to cause that definition to create a generator function instead of a normal function.

However, you can achieve what you had intended by simply defining an inner generator function:

>>> def foo(gen=False):
...     if not gen:
...         return list(range(10))
...     else:
...         def foo():                 # the inner generator
...             for i in range(10):
...                 yield i
...         return foo()

>>> foo()    
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> foo(gen=False)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> foo(gen=True)
<generator object foo.<locals>.foo at 0x7f350c7563b8>
>>> g = foo(gen=True)
>>> next(g)
0

This time, the yield statement turns the inner foo into a generator. The outer foo remains a normal function.