For loop not working inside the function

So I kinda in a struggle to find the solution to this. I have callbacks to display values from a dictionary to multiple html.Divs. I saw an example using for loop for callbacks on one of the Dash documentations and decided to use it on my project so that I can avoid long list of inputs and outputs inside @app.callback.

for i in range(7):
    @app.callback(
        [Output('gd{}'.format(i+1), 'children'),
         Output('gd{}-type'.format(i+1), 'children'),
         Output('gd{}-unit'.format(i+1), 'children')],
        Input('ret_data', 'data'))
    def glob_output(data):
        results = simulation_store(**data)
        g = results[0]
        gl = list(g.keys())
        # print(i)
        val, desc, unit = g[gl[i]]['value'], gl[i], \
                          g[gl[i]]['units']

        return val, desc, unit

I have tons of outputs and using loop would be the way since the naming convention is just the numbering of the ids. The results from the function would have two dictionary inside a list, the first on the list would be the g. Since it is a dictionary and inside the dict there’s a nested dictionary, I kinda make a new list from the keys of the g, which I call gl.

The idea is that at each iteration, the outputs will have its value, description and units shown from the dictionary ‘g’. But when running the code, all outputs show the value, description and units of the last iteration, (in this case, 6 which is the last iteration from range (7)).

Simple test print(i) made that clear that each iteration it prints ‘6’ and the loop isnt working inside the function. But I know for a fact the loop works with the @app.callbacks since all outputs show the data from the dict, but they all show the same data, which is the last iteration of the gl list. Anyone has any idea what I did wrong?

@ayam ,
I did something similar on a project recently. Rather than placing the for loop around the whole decorated function, I created an array first, and then fed that into the decorator. So, in your example, you might try something like this:

outputs = list(map(lambda x: Output('gd{}'.format(x+1), 'children'), range(7)))
outputs.append(list(map(lambda x: Output('gd{}-type'.format(x+1), 'children'), range(7))))
outputs.append(list(map(lambda x: Output('gd{}-unit'.format(x+1), 'children'), range(7))))

@app.callback(outputs, Input('ret_data', 'data'))
def glob_output(data):
...

Rather than using the list(map with a lambda, you should be able to accomplish the same thing with a simple list comprehension as well.

Hope that helps.

1 Like

The behavior you see is expected due to the way scopes work in Python. I believe if you replace def glob_output(data): by def glob_output(data, i=i): it will work as you intended.

1 Like

@Emil oh woww it works! Thank you so much for pointing it out! I guess I have to go back to trying to grasp the theory and basic concepts of programming again so that I won’t make the same mistake again. I thought i had it all sorted out :sweat_smile:

1 Like

@kazshak
thank you so much for your input. I was thinking of doing something similar to that , as I have implemented similar solution to one of the callbacks in my project as I have many of them. I came across the loop example from dash documentation and tried to translate it to my project because I think it looks neater than having to create global list outside of the function. But I think @Emil answer is the right one for my problem. :smiley: Thank you again for your input, I really appreciate it !!

It is because the value of i changes as the loop executes. Hence at the time you execute the callback, i will have the last value (6). By adding the i=i part you capture the current value of i and use that as a the default value for a new local (with respect to the callback) variable i. So, the behaviour is logical. Kind off. But I understand your confusion - I was also puzzled the first time I encountered this behavior :smiley:

1 Like