Callback looping

Why would the code below not work? I’m looping through the variable names in the list dist_vars, assigning them to a series of dcc.Graphs with id’s of dist1, dist2, dist3, etc. When i run this however, all of the graphs are updated with the result for the last var in the list. In other words the result for c=1,2,3,4 in dist1,dist2,dist3,dist4 all get overwritten with the result of the loop when c=5…

dist_vars = ['length','depth','proppant_sand','Stages Actual','total_fluid']
dist_labs = ['Length (m)', 'Depth (m)', 'Total Proppant (t)', 'Stages (Actual)', 'Total Fluid (t)']

   
for c in range(0,len(dist_vars)):
    print('dist{}'.format(c+1))
    @app.callback(
        dash.dependencies.Output('dist{}'.format(c+1),'figure')
        ,[dash.dependencies.Input('clickinfo','children')])
    def update_dist(idx):
        idx = json.loads(idx)
        if idx != []:
            vcons = cons2[cons2['rowindex'].isin(idx)]
        else:
            vcons = cons2
            nl = vcons['WellName']
        return(do_hist_plus(namelist = nl, var = dist_vars[c], xtitle = dist_labs[c]))

Figured it out - needed to pass c as a parameter into the update_dist function itself.

Without a fully working example I can’t say for sure.

But one thing I can tell you is that is that when you look up global variables (in this case c) inside a function they are done at execution time not definition time.

Look at this example and try and think what it will print before you run it:

my_funcs = []
for x in range(10):
    def foo():
        return x + 1
    my_funcs.append(foo)

for my_func in my_funcs:
    print(my_func())

A common solution to this is to use partials: functools — Higher-order functions and operations on callable objects — Python 3.7.12 documentation . It freezes your arguments to a function so you can call it later with old arguments. Rewriting this example is looks like this:

from functools import partial

my_funcs = []
for x in range(10):
    def foo(y):
        return y + 1
    my_funcs.append((partial(foo, y=x)))

for my_func in my_funcs:
    print(my_func())

I’ve rewritten your code to use a partial, hopefully it helps:

import json
from functools import partial
import dash


def update_dist(idx, c):
    idx = json.loads(idx)
    if idx != []:
        vcons = cons2[cons2['rowindex'].isin(idx)]
    else:
        vcons = cons2
        nl = vcons['WellName']
    return do_hist_plus(namelist = nl, var = dist_vars[c], xtitle = dist_labs[c])


app = dash.Dash()
dist_vars = ['length', 'depth', 'proppant_sand', 'Stages Actual', 'total_fluid']
dist_labs = ['Length (m)', 'Depth (m)', 'Total Proppant (t)', 'Stages (Actual)', 'Total Fluid (t)']

for c in range(0, len(dist_vars)):
    print('dist{}'.format(c+1))
    app.callback(
        dash.dependencies.Output('dist{}'.format(c+1),'figure'),
        [dash.dependencies.Input('clickinfo','children')]
    )(partial(update_dist, c=c))
1 Like

Thanks for taking the time to look it over. I managed to get it working simply by passing the loop counter - c - to the callback function as an explicit parameter - k. I imagine this forces the loop-generated functions to use the current value of c each pass through the loop and retain it, rather than creating duplicate functions that simply all reference the value of c as it increments. That’s my totally non-technical interpretation of what’s going on:

for c in range(0,len(dist_vars)):
    print('dist{}'.format(c+1))
    @app.callback(
        dash.dependencies.Output('dist{}'.format(c+1),'figure')
        ,[dash.dependencies.Input('clickinfo','children')])
    def update_dist(idx,k=c):
        idx = json.loads(idx)
        if idx != []:
            vcons = cons2[cons2['rowindex'].isin(idx)]
        else:
            vcons = cons2
            nl = vcons['WellName']
            return(do_hist_plus(namelist = nl, var = dist_vars[k], xtitle = dist_labs[k]))

Yes, the k=c step you are performing in the function arguments is storing c as a default value at definition time. It’s not a standard way of handling this situation but if it works for you go for it!