# Wrapping my head around generators

I am going on with refactoring my code base while migrating from Python 2 to Python 3. I am using generators to make reusable components this time as I am more comfortable with them this time then last time. But there are times when I am stuck at their use. Like below example. These two are essentially same with one infinitely generating and other with a limit.

Is there a way to make these as one function? Normally an extra optional parameter as limit will do the trick but with generators I am not sure how to use them. Is there some way to do that?

def fibonacci_inf(a, b):
"""Lazily generates Fibonacci numbers Infinitely"""
while True:
yield b
a, b = b, a + b

def fibonacci(a, b, num):
"""Lazily generates Fibonacci numbers"""
while b < num:
yield b
a, b = b, a + b

-
I don't use Python much, but why wouldn't an optional argument work here? Pseudocode: while(!optionalArgumentExists || optionalArgumentCondition). –  Ocelot20 Mar 30 at 15:27
@Ocelot20 Exactly my point. But I couldn't wrap my head around actual syntax without making this a mess. –  Aseem Bansal Mar 30 at 15:28

You could just implement the infinite generator, and use itertools.takewhile to limit it:

>>> list(itertools.takewhile(lambda x : x < 8, fibonacci_inf(0,1)))
[1, 1, 2, 3, 5]

-
Having spent some time with Haskell I can really appreciate this now. Another reason why I should go over the standard library again once in a while. –  Aseem Bansal Mar 30 at 17:28

Something like this should work:

def sequence(start, end = None):
loopIndefinitely = end is None

curr = start
while loopIndefinitely or curr <= end:
yield curr
curr += 1

## Prints: 1 2 3 4 5 6 7 8 9 10
for i in sequence(1, 10):
print(i)

## Prints: 1 2 3 4 5 6 7 8 9 10
for j in sequence(1):
if(j > 10):
break
else:
print(j)


Used sequence instead since the variable names more clearly show the use vs. a, b, num.

I think it's important to:

1. Clearly define what argument values lead to what functionality (for example, defining a clearly named variable like loopIndefinitely to show immediately what an end value of None means).
2. Determine when it's appropriate to use an optional argument. This one is a bit tougher, but it may have been clearer to define two functions called range and infiniteSequence. In this example the code is pretty simple to share or even duplicate, but with a more complex function you may want to have two functions that both point to a third one that has most of the reusable code.
-
One variation when the logic is this simple is to check the optional argument once and branch--either to separate functions as you suggest or inline. It's a minor performance improvement but may help to highlight how the optional argument applies. –  David Harkness Mar 30 at 16:19