8
\$\begingroup\$

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
\$\endgroup\$
2
  • \$\begingroup\$ I don't use Python much, but why wouldn't an optional argument work here? Pseudocode: while(!optionalArgumentExists || optionalArgumentCondition). \$\endgroup\$
    – Ocelot20
    Mar 30, 2014 at 15:27
  • \$\begingroup\$ @Ocelot20 Exactly my point. But I couldn't wrap my head around actual syntax without making this a mess. \$\endgroup\$ Mar 30, 2014 at 15:28

2 Answers 2

10
\$\begingroup\$

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]
\$\endgroup\$
1
  • \$\begingroup\$ 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. \$\endgroup\$ Mar 30, 2014 at 17:28
5
\$\begingroup\$

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.
\$\endgroup\$
1
  • \$\begingroup\$ 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. \$\endgroup\$ Mar 30, 2014 at 16:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.