0

I'm running into issues with Python dispatcher definitions changing each time I add a new function to the dispatcher. An example:

def dispatcher_create():
    dispatcher = {}
    for i in range(5):
        def square():
            return i**2
        dispatcher[i] = square
    for j in range(5):
        print dispatcher[j]()
    return dispatcher

This code prints out the value 16 five times. I was hoping it would print out 0 1 4 9 16 instead. I'm sure it's an issue with me redefining square every time, but I'm not sure how best to fix it.

user2569332
  • 555
  • 1
  • 4
  • 12

3 Answers3

2

The i in return i**2 is bound to the name i, not the value i.

Try this to create a new variable, bound to the appropriate value:

def dispatcher_create():
    dispatcher = {}
    for i in range(5):
        def square(i=i):
            return i**2
        dispatcher[i] = square
    for j in range(5):
        print dispatcher[j]()
    return dispatcher

dispatcher_create()
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
2

No, redefining square() every time is doing what you want, which you can check by printing the contents of dispatcher: all the functions in it will have different id's as desired.

The problem is that you're creating a closure so when you call any of the functions stored in dispatcher they are accessing the latest value of i, rather than using the value that i had when they were defined.

Robᵩ has shown one way around that, by passing i as an arg to square(); here's another way: using another closure which takes i as an arg so it can preserve it for the squaring function it makes.

def dispatcher_create():
    dispatcher = {}
    def make_square(j):
        def square():
            return j**2
        return square

    for i in range(5):
        dispatcher[i] = make_square(i)
    return dispatcher

dd = dispatcher_create()
print dd

for j in range(5):
    print dd[j]()

typical output

{0: <function square at 0xb73a0a3c>, 1: <function square at 0xb73a0dbc>, 2: <function square at 0xb73a0d84>, 3: <function square at 0xb73a5534>, 4: <function square at 0xb73a517c>}
0
1
4
9
16

Robᵩ's version is a little simpler, but this version has the advantage that the functions in dispatcher have the desired argument signature, i.e., they take no argument, whereas Robᵩ's functions take a single argument with a default value that you can over-ride.

FWIW, you could use i instead of j as the parameter for make_square(), since it's just a local variable to make_square(). OTOH, using i there shadows the i in the outer scope, and I feel that using j is slightly less confusing.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
0

I believe it has to do with scope. The i variable remains defined after the for cycle it is used in. If you print the value of i after the for cycle, you see it is 4. If you then call the functions in the next for cycle, the value of i that is in the current scope is used.

As for the solution, i think functools.partial would be a good choice.

from functools import partial

def dispatcher_create():
    dispatcher = {}
    for i in range(5):
        def square(value):
            return value**2
        dispatcher[i] = partial(square, i)
    for j in range(5):
        print dispatcher[j]()
    return dispatcher
Jiří Kantor
  • 744
  • 1
  • 5
  • 12