#python #callback #plotly-dash
#python #обратный вызов #plotly-dash
Вопрос:
Я создал приложение dash, в котором новые визуализации могут быть загружены по требованию из классов python. Для статического содержимого без обратных вызовов это работает нормально.
При попытке загрузить обратные вызовы они не активны при первом вызове страницы. Только после одной перезагрузки веб-страницы содержимое по требованию работает должным образом.
Я предполагаю, что это вызвано регистрацией обратного вызова после выполнения app.run_server()
. Предварительная загрузка, однако, не является опцией, поскольку классы в моем приложении требуют дополнительных входных аргументов, которые известны только во время выполнения сервера.
Итак, мой вопрос: кто-нибудь знает способ, как успешно зарегистрировать обратные вызовы после того, как сервер уже запущен? Или другой способ добиться такого же поведения?
Вот мой подход до сих пор, сравнивая предварительно загруженное поведение с поведением по требованию:
from dash import Dash, html, dcc, Input, Output
class OnDemandContent:
def __init__(self):
# prefix to distinguish on-demand from preloaded components
self.prefix = self.__class__.__name__
# registering a class method as a callback,
# from https://community.plotly.com/t/putting-a-dash-instance-inside-a-class/6097/4
app.callback(Output(f'{self.prefix}counter', 'children'),
Input(f'{self.prefix}interval', 'n_intervals'))(self.update)
def get_layout(self):
return html.Div([
html.Div(['Counter has not started yet'], id=f'{self.prefix}counter'),
dcc.Interval(id=f'{self.prefix}interval', interval=100),
])
def update(self, n_clicks):
return f'Counting: {n_clicks}'
class PreloadedContent(OnDemandContent):
pass
# callback exceptions are suppressed, because some elements are loaded on demand
app = Dash(__name__, suppress_callback_exceptions=True)
app.layout = html.Div([
html.Div('Preloaded Content is not shown yet', id='content-preloaded'),
html.Button('Show PreloadedContent', id='show-preloaded', n_clicks=0),
html.Hr(),
html.Div('OnDemandContent is not shown yet', id='content-on-demand'),
html.Button('Show OnDemandContent', id='show-on-demand', n_clicks=0),
])
# initialize preload class and hence activate its callbacks before running the server.
# in my app, this is not an option, because the class would require further input
# which is given by the user during runtime
preloaded_instance = PreloadedContent()
# callbacks for "show x" buttons
@app.callback(
Output('content-on-demand', 'children'),
Input('show-on-demand', 'n_clicks'),
prevent_initial_call=True,
)
def update_output(_):
return OnDemandContent().get_layout()
@app.callback(
Output('content-preloaded', 'children'),
Input('show-preloaded', 'n_clicks'),
prevent_initial_call=True,
)
def update_output(_):
return preloaded_instance.get_layout()
app.run_server(port=80, debug=True)
Комментарии:
1. Вы не можете добавлять обратные вызовы во время выполнения в Dash. Однако вы можете добиться некоторого динамического поведения, используя обратные вызовы, соответствующие шаблону. dash.plotly.com/pattern-matching-callbacks
2. @emher спасибо за предложение, но мне все равно нужно будет зарегистрировать эти обратные вызовы, соответствующие шаблону, перед запуском сервера, что значительно ограничит их гибкость. Я надеялся, что после запуска сервера все еще есть какая-то возможность регистрации, например app.refresh_server или что-то подобное (чего я также не смог найти, так что вы можете быть правы, что это невозможно)
Ответ №1:
Подумав о моей идее настраиваемых экземпляров с информацией о том, что регистрация обратных вызовов во время выполнения будет невозможна (один из редких случаев, когда «это невозможно сделать» подтолкнул меня в правильном направлении), я увидел, что это скорее вопрос пересмотра моего подхода:
Вместо того, чтобы полностью создавать экземпляры классов, когда они мне нужны, я вместо этого создаю их без настройки перед запуском сервера (следовательно, регистрирую обратные вызовы в нужное время), и только модифицирую этот экземпляр с помощью пользовательского содержимого с помощью его get_layout()
метода во время выполнения.
Вот измененный код:
from dash import Dash, html, dcc, Input, Output, State
class PreloadedContent:
def __init__(self):
app.callback(Output(f'counter', 'children'),
Input(f'interval', 'n_intervals'))(self.update)
def get_layout(self, custom_content):
self.custom_content = custom_content
return html.Div([
html.Div(['Counter has not started yet'],
id=f'counter'),
dcc.Interval(id=f'interval', interval=100),
])
def update(self, n_clicks):
return f'Counting with {self.custom_content}: {n_clicks}'
# callback exceptions are suppressed, because some elements are loaded on demand
app = Dash(__name__, suppress_callback_exceptions=True)
app.layout = html.Div([
html.Div('Preloaded Content is not shown yet', id='content-preloaded'),
html.Hr(),
dcc.Input(id="input", placeholder="customize instance"),
html.Hr(),
html.Button('Show PreloadedContent', id='show-preloaded', n_clicks=0),
])
preloaded_instance = PreloadedContent()
@app.callback(
Output('content-preloaded', 'children'),
Input('show-preloaded', 'n_clicks'),
State('input', 'value'),
prevent_initial_call=True,
)
def update_output(_, custom_content):
return preloaded_instance.get_layout(custom_content)
app.run_server(port=80, debug=True)