Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CantHaveMultipleOutputs limit #153

Closed
sdementen opened this issue Nov 1, 2017 · 11 comments
Closed

CantHaveMultipleOutputs limit #153

sdementen opened this issue Nov 1, 2017 · 11 comments

Comments

@sdementen
Copy link

I often get the CantHaveMultipleOutputs exception due to different callbacks sharing the same output.
This constraint is quite limiting as it requires to refactor quite a bit UI logic that would be simpler if multiple callback could change the same Output.

Is this limit a fundamental limit ? or are they plans to lift this in the coming future ?

@ned2
Copy link
Contributor

ned2 commented Nov 7, 2017

I'm curious about this too, it seems like it would be quite useful to have multiple callbacks targeting the same property of an element. In fact I think this is going to be quite limiting in some situations. Here for example.

I foresee that there could be some confusing scenarios where multiple callbacks are created that target the one output but also share an overlapping input/event, meaning that two callbacks will be triggered and the output element will be updated twice, with the returned value of the one that happens to be handled and received first being clobbered by the second. But this could be detected in the callback validation and the user presented with a warning.

I've been looking through the way callbacks are registered in dash.py... would the main issue being having to change the way callbacks are keyed in the callback_map to avoid collisions? The keys could be changed to be the entire callback signature. eg output_element_id--input1_id--input2_id--state1_id--state2_id etc. making them unique Or is there something more fundamental that I'm missing?

Edit: Oh I see, you currently need to have already looked up the callback_map to get the information you'd need to reconstruct that signature I proposed...

@chriddyp
Copy link
Member

chriddyp commented Nov 7, 2017

Allowing multiple callbacks to share the same output causes too much ambiguity (what happens when there are overlapping inputs?) and violates the zen of python rule "there should be one way to do things".

AFAIK, users are only running into the CantHaveMultipleOutputs limit because they would like to update an output differently depending on which inputs change. Would this be solved by supporting PrevState (as in plotly/dash-renderer#25 and #140)?

@sdementen
Copy link
Author

The #140 would allow to avoid global variables to get what button has been clicked but it looks still complex to get a basic feature. Specially for buttons, using an Event makes the most sense (vs getting the number of time the button was clicked) but we would need to get a True/False flag parameter representing this event in the callback

@app.callback(Output(...), events=[Event('button-id','click')])
def mycb(button_clicked):
    # button_clicked is True if the event was fired

Or do you see issues with this approach (which is not backward compatible...)

@ned2
Copy link
Contributor

ned2 commented Nov 8, 2017

I think there's two things going on here:

  1. I agree with @sdementen, it would be great if there was more context around which element triggered the event. More generally, I've been struggling with the problem of tracking which inputs/states in the callback *args list correspond to which elements in the layout. My current approach has been to iterate over a synchronised list of (value, data_dict), where data_dict contains the context I need to know which element it was and how to process it. This is a little cumbersome though. It would be much easier if the callback function were passed component registration dicts for each input/state, with the keys: 'id', 'property', 'value'. (Maybe I'll make another issue for that)

  2. @chriddyp regarding the need for multiple outputs, how would you currently suggest handling a situation where you have say 3 different models that is driving the one Graph element and each model has a different range of inputs. So you might have a dropdown that selects from model1, model2, and model3, and this triggers the insertion of a different set of controls for each model in the UI, where each set of controls have different types of inputs.

From a pure abstraction point of view, I would want to organise my code like this:

model1(text_input1, text_input2)
model2(text_input1, bool_input1)
model3(dropdown_input1, int_input1)

However, in order to target the same Graph element at the moment in Dash, I would need to combine them all into the one callback function and tease apart the different inputs into my different model functions. One of the nice things about Dash is thinking of callbacks as modular functions that correspond to how existing Python functions/abstractions, but we lose that in this case. (Also, all the inputs are going to need to be present in the layout too... so I can't swap them in and together with a callback, I'd have to hide the ones that aren't relevant to the current model)

PrevState might help tease apart which inputs were selected, but you still have a bit of a mess regarding the abstractions.

@sdementen
Copy link
Author

Regarding the dependencies ambiguities in case of multiple outputs, what happens today in the following case


app.layout = html.Div([
    dcc.Interval(id="interval1", interval=20000),
    html.Div(id="div1"),
    html.Div(id="div2"),
])

Out1 = Output("div1","children")
Inp1 = Input("div1","children")
Out2 = Output("div2","children")
Int1 = Event("interval1","interval")

@app.callback(Out1, inputs=[], events=[Int1])
def f1(): pass

@app.callback(Out2, inputs=[Inp1], events=[Int1])
def f2(): pass

So when the clock ticks, we would ideally (in some sense of "ideally") compute that:

  • f2 depends on an input that is the output of f1
  • we must call first f1
  • and then f2
    which is what i see in practice when running the example (when starting, f2 is called but I do not know why ...).

Should I understand that the logic for dependencies is coded in js ?

Could it be possible to adapt this logic to take into account multiple outputs with a "resolution" order that would be based on a "order/priority/z-level" integer argument given in the Output (e.g. Output("dev1","children",order=20)) that would just rank the order of the calls ? by default the order could be autoincremented so that the order would follow the order of the callback declarations.

byronz added a commit that referenced this issue Apr 23, 2019
another round of dev-tools edits
@johnnyheineken
Copy link

I ran into the CantHaveMultipleOutputs problem when I tried to create dynamic controls, such as here
https://github.com/plotly/dash-recipes/blob/master/dash_dynamic_controls.gif
however I wanted all of the controls to modify a single html.Div
(such as storing changes of the dropdown into a hidden Div as proposed here: https://github.com/plotly/dash-recipes/blob/master/save-previous-option.py)

Removing the CantHaveMultipleOutputs limit would simplify the code a lot.

@alexcjohnson
Copy link
Collaborator

@johnnyheineken have you seen dash.callback_context? (see https://dash.plot.ly/faqs "How do I determine which Input has changed?") Does that address your use case?

@alexcjohnson
Copy link
Collaborator

Closing - callback_context handles most of the concerns raised above. It's true that in some cases things could be simpler - at least initially - with multiple callbacks targeting the same output. But as soon as the app gets a little more complex I think being forced to fit them into a single callback actually avoids a lot of headaches, and it's certainly a lot clearer from our side.

@johnnyheineken
Copy link

johnnyheineken commented Jul 28, 2020

@alexcjohnson :
I returned back to this issue.
callback_context does not solve this problem - I need to modify a single object from multiple dropdowns.
These dropdowns can be also dynamically created.

Is there any progress on this issue? I know that there was an update regarding dynamic controls, so I am interested whether this issue was adressed as well

@alexcjohnson
Copy link
Collaborator

@johnnyheineken Check out https://dash.plotly.com/pattern-matching-callbacks. This lets you connect a callback to a flexible number of inputs and outputs, which can change at any time.

@Mereep
Copy link

Mereep commented Feb 15, 2022

Is there any update on this?

How is the propsed way for updating a Output(LocationElement, 'search') from different callbacks (this shouldn't be too uncommon)? I cannot collect all inputs in one place (one callback) since they are dynamically created and trying to reference those throws another error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants