2

The following code took me by surprise. I was hoping that I could write a function that might or might not act as a generator.

def mayGen(limit=5, asGenerator=False):
    result = []
    for i in range(1, limit + 1):
        n = i * i
        if asGenerator:
            yield n
        else:
            result.append(n)
    return result

print(mayGen(5, False))

for x in mayGen(5, True):
    print(x)

But no, the output of this program is

<generator object mayGen at 0x7fa57b6ea7b0>
1
4
9
16
25

Calling mayGen with asGenerator=False is simply useless. It seems that the mere existence of a yield statement, regardless of whether it is executed, radically changes the behavior of the containing function.

So what if mayGen was actually big and complicated and I wish I could ask it to either function as a generator, or write its computed output to a file? Is there a well-traveled path here? How are people handling this sort of thing?

Joymaker
  • 813
  • 1
  • 9
  • 23
  • 2
    "It seems that the mere existence of a yield statement, regardless of whether it is executed, radically changes the behavior of the containing function." - well, yeah. Python can't wait until a function yields before deciding it's a generator function. Among other problems, that would make it impossible to create an empty generator, requiring tons of awkward and error-prone special-case handling for empty input. – user2357112 Jul 27 '22 at 01:07
  • 1
    My understanding is the presence of `yield` anywhere in the code makes it a generator. Furthermore, `return` acts slightly differently in a generator as compared to a function, so I'm not sure the last line makes sense. – njp Jul 27 '22 at 01:07
  • 2
    Does this answer your question? [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) – Abhishake Gupta Jul 27 '22 at 01:13
  • I think it is a bad thing to give any function a true/fasle parameter . You should write the if else before calling the func and if true call the func with yield if not then no yield – Hi computer Jul 27 '22 at 01:16
  • I think true/false parameters are breaking single responsibility principal – Hi computer Jul 27 '22 at 01:19
  • 8
    This is opinionated so I won't write it in an answer, but I would say this is an anti-pattern. "Just" write the generator function, and if you want to get all values from the generator use `list(my_generator())` – Iguananaut Jul 27 '22 at 01:19
  • @wim I think you misunderstood what OP's asking, so I reopened it and posted an answer. Your links might be useful for context though – wjandrea Jul 27 '22 at 01:55

2 Answers2

3

Just write a generator. You can always take its output and do whatever you want with it: put it in a list, write it to a file, etc. Although, if that's what you're going to be using it for a lot, you might want to write a wrapper function to do it for you, for example:

def willGen(limit=5):
    for i in range(1, limit+1):
        n = i * i
        yield n

def willReturnList(*args, **kwargs):
    return list(willGen(*args, **kwargs))
wjandrea
  • 28,235
  • 9
  • 60
  • 81
1

Edit: For a number of reasons mentioned in comments, this is probably not a great idea. In any event, you can write this:

def mayGen(limit=5, asGenerator=False):
    def _generate(limit):
        for i in range(1, limit + 1):
            n = i * i
            yield n
    def _return(limit):
        result = []
        for i in range(1, limit + 1):
            n = i * i
            result.append(n)
        return result
    if asGenerator:
        return _generate(limit)
    return _return(limit)

Edit: to simiplify even further, you should probably just return list(_generate(limit)) in the final line.

philosofool
  • 773
  • 4
  • 12
  • 3
    This kinda defeats the purpose of not having to write two functions. You could simply do `if asGenerator: return _generate(limit); else return list(_generate(limit))`, although it would be much more preferable to always have the function return a generator, and a separate function return the list (or wrap the generator in `list(...)`) – Pranav Hosangadi Jul 27 '22 at 01:27
  • 3
    I didn't say it was a good idea, I just say you could do it. This answers the question. If someone asks how to make Python crash, we can still tell them. – philosofool Jul 27 '22 at 01:33
  • Even if you accept the anti-pattern of returning a generator or a list, I'm saying you should define a single function, `_generate` and _call that function_ to create a list instead of defining _two functions_ that do basically the same thing. – Pranav Hosangadi Jul 27 '22 at 02:20
  • Whether or not it ever proves useful, thanks for the insight! That's a nice trick. – Joymaker Jul 28 '22 at 15:53