1

how can i get rid of the globals in fib_gen2? I don't want to use native generators or classes per this gist, this is an academic exercise, though I am interested improvements in any of the implementations.

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    global a; a = 1
    global b; b = 1
    def next():
        global a; global b;
        r = a
        a, b = b, a + b
        return r
    return next

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)
1
  • 1
    "I don't want to use native generators..." if you're doing this kind of thing at all, it's presumably because of an interest in functional programming; in which case, you should appreciate that using generators will allow you to avoid sharing state between the generator instances. Commented Feb 20, 2012 at 2:03

3 Answers 3

10

In Python 3.x, you can use the nonlocal statement:

def fib_gen2():
    a = b = 1
    def next():
        nonlocal a, b
        a, b = b, a + b
        return b - a
    return next

In Python 2.x, you'll need to use some hack:

def fib_gen2():
    ab = [1, 1]
    def next():
        ab[:] = ab[1], ab[0] + ab[1]
        return ab[1] - ab[0]
    return next

This unsatisfactory situation was the very reason for the introduction of nonlocal in Python 3.x.

Python has no variable declarations, so it has to figure out the scope of each variable itself. It does so by a simple rule: If there is an assignment to a name inside a function, this name is local to that function -- except it is explicitly declared global or nonlocal. In the second example, there is no assignment to the name ab -- the list is modified, but the name is not reassigned. Thus the scope is the enclosing function.

Sign up to request clarification or add additional context in comments.

3 Comments

whats the reasoning behind this behavior? I'm a bit confused as to why it is this way.
@DustinGetz: Added a bit of explanation.
Python really only has local and global scopes, without lexical scoping. Assigning to a variable effectively declares it as local, unless explicitly marked otherwise. You can't "re-assign" to a non-local, non-global variable in 2.x, because it isn't global (so you can't mark it), and assignment creates a new local. The hack avoids this by mutating the object contained by the ab variable instead of attempting to replace it.
1

If I really had to avoid generators, I'd probably do this:

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen3():
    def step():
        r = step.a
        step.a, step.b = step.b, step.a + step.b
        return r
    step.a = 1
    step.b = 1
    return step

>>> ftake(fib_gen3(), 10)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Comments

1

This is kind of cheating so hopefully someone can give you a better answer but:

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    fib_gen2.a = 1
    fib_gen2.b = 1
    def next():
        r = fib_gen2.a
        fib_gen2.a, fib_gen2.b = fib_gen2.b, fib_gen2.a + fib_gen2.b
        return r
    return next

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.