Python - 在 Dash 回调中重用函数
Python - Reuse functions in Dash callbacks
我正在尝试在 Python Dash 框架中制作一个应用程序,它允许用户 select 列表中的一个名称并使用该名称填充其他两个输入字段。用户可以在六个地方 select 来自(相同)列表的名称,因此总共需要执行 12 个回调。我的问题是,如何使用单个函数定义来提供多个回调?
正如我在其他地方看到的那样 (),人们在执行多个回调时会重复使用相同的函数名称,例如
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon3-health', 'value'),
[Input('rp-mon3-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
这是大量相同的重复,如果我需要稍后实施修复,那就太糟糕了。理想情况下,我可以做类似的事情:
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
@app.callback(
Output('rp-mon2-health', 'value'),
[Input('rp-mon2-name', 'value')]
)
@app.callback(
Output('rp-mon3-health', 'value'),
[Input('rp-mon3-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
然而,上面的结果是前两个没有回调,只有最后一个。我的代码如下。
import json
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
monster_data = json.loads('''[{
"name": "Ares Mothership",
"health": 14,
"transition": 2
},{
"name": "Cthugrosh",
"health": 7,
"transition": 3
}]''')
monster_names = [{'label': m['name'], 'value': m['name']} for m in monster_data]
monster_names.append({'label': 'None', 'value': ''})
app = dash.Dash(__name__)
def gen_monster(player, i):
name = 'Monster #%d: ' % i
id_gen = '%s-mon%d' % (player, i)
output = html.Div([
html.Label('%s Name ' % name),
html.Br(),
dcc.Dropdown(
options=monster_names,
value='',
id='%s-name' % id_gen
),
html.Br(),
html.Label('Health'),
html.Br(),
dcc.Input(value=11, type='number', id='%s-health' % id_gen),
html.Br(),
html.Label('Hyper Transition'),
html.Br(),
dcc.Input(value=6, type='number', id='%s-state' % id_gen),
], style={'border': 'dotted 1px black'})
return output
app.layout = html.Div(children=[
html.H1(children='Monsterpocalypse Streaming Stats Manager'),
html.Div([
html.Div([
html.Label('Left Player Name: '),
dcc.Input(value='Mark', type='text', id='lp-name'),
gen_monster('lp', 1),
html.Br(),
gen_monster('lp', 2),
html.Br(),
gen_monster('lp', 3)
], style={'width': '300px'}),
html.Br(),
html.Div([
html.Label('Right Player Name: '),
dcc.Input(value='Benjamin', type='text'),
gen_monster('rp', 1),
html.Br(),
gen_monster('rp', 2),
html.Br(),
gen_monster('rp', 3)
], style={'width': '300px'})
], style={'columnCount': 2}),
html.Div(id='dummy1'),
html.Div(id='dummy2')
])
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon1-state', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['transition']
else:
return 6
if __name__ == '__main__':
app.run_server(debug=True)
你可以这样做:
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def monster_1_callback(*args, **kwargs):
return update_health(*args, **kwargs)
@app.callback(
Output('rp-mon2-health', 'value'),
[Input('rp-mon2-name', 'value')]
)
def monster_2_callback(*args, **kwargs):
return update_health(*args, **kwargs)
@app.callback(
Output('rp-mon3-health', 'value'),
[Input('rp-mon3-name', 'value')]
)
def monster_3_callback(*args, **kwargs):
return update_health(*args, **kwargs)
现在包含逻辑的函数只写了一次,其他函数都是简单的传递,您永远不需要更新。
我遇到了完全相同的问题。仅输入和输出 ID 不同的大量回调。以下对我有用(我将从我的代码中提供一个示例,但想法是一样的)
def rangeslider_tocalendar(output, input):
@app.callback([Output(output, 'start_date'),
Output(output, 'end_date')],
[Input(input, 'value')])
def repeated_callback(range_slider):
cal_start = datetime.date.fromordinal(range_slider[0])
cal_end = datetime.date.fromordinal(range_slider[1])
return cal_start, cal_end
rangeslider_tocalendar('date-range', 'range-slider')
我将重复回调包装在一个函数中 rangeslider_tocalendar()
。然后我只调用包装函数并传入输入和输出 id。让意大利面远离我的盘子。
逻辑上等效的方法,但 重复代码较少,将在循环中分配回调,
def update_health(monster):
if not monster:
return 11
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
for i in range(1, 13):
app.callback(Output(f'rp-mon{i}-health', 'value'),
[Input(f'rp-mon{i}-name', 'value')])(update_health)
一个更规范的选择是使用 Dash 的 pattern-matching callback 功能,
@app.callback(Output(dict(id=MATCH, type='rp-mon-health'), 'value'),
[Input(dict(id=MATCH, type='rp-mon-name'), 'value')])
def monster_callback(monster):
if not monster:
return 11
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
除了清晰简洁的语法外,这种方法还具有扩展到 动态 组件数量的优势。也就是说,如果您在运行时 add/remove input/output 组件对(例如,由于其他一些用户选择,您想添加另一个选项),模式匹配方法仍然有效。固定的回调分配显然不会。
我正在尝试在 Python Dash 框架中制作一个应用程序,它允许用户 select 列表中的一个名称并使用该名称填充其他两个输入字段。用户可以在六个地方 select 来自(相同)列表的名称,因此总共需要执行 12 个回调。我的问题是,如何使用单个函数定义来提供多个回调?
正如我在其他地方看到的那样 (
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon3-health', 'value'),
[Input('rp-mon3-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
这是大量相同的重复,如果我需要稍后实施修复,那就太糟糕了。理想情况下,我可以做类似的事情:
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
@app.callback(
Output('rp-mon2-health', 'value'),
[Input('rp-mon2-name', 'value')]
)
@app.callback(
Output('rp-mon3-health', 'value'),
[Input('rp-mon3-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
然而,上面的结果是前两个没有回调,只有最后一个。我的代码如下。
import json
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
monster_data = json.loads('''[{
"name": "Ares Mothership",
"health": 14,
"transition": 2
},{
"name": "Cthugrosh",
"health": 7,
"transition": 3
}]''')
monster_names = [{'label': m['name'], 'value': m['name']} for m in monster_data]
monster_names.append({'label': 'None', 'value': ''})
app = dash.Dash(__name__)
def gen_monster(player, i):
name = 'Monster #%d: ' % i
id_gen = '%s-mon%d' % (player, i)
output = html.Div([
html.Label('%s Name ' % name),
html.Br(),
dcc.Dropdown(
options=monster_names,
value='',
id='%s-name' % id_gen
),
html.Br(),
html.Label('Health'),
html.Br(),
dcc.Input(value=11, type='number', id='%s-health' % id_gen),
html.Br(),
html.Label('Hyper Transition'),
html.Br(),
dcc.Input(value=6, type='number', id='%s-state' % id_gen),
], style={'border': 'dotted 1px black'})
return output
app.layout = html.Div(children=[
html.H1(children='Monsterpocalypse Streaming Stats Manager'),
html.Div([
html.Div([
html.Label('Left Player Name: '),
dcc.Input(value='Mark', type='text', id='lp-name'),
gen_monster('lp', 1),
html.Br(),
gen_monster('lp', 2),
html.Br(),
gen_monster('lp', 3)
], style={'width': '300px'}),
html.Br(),
html.Div([
html.Label('Right Player Name: '),
dcc.Input(value='Benjamin', type='text'),
gen_monster('rp', 1),
html.Br(),
gen_monster('rp', 2),
html.Br(),
gen_monster('rp', 3)
], style={'width': '300px'})
], style={'columnCount': 2}),
html.Div(id='dummy1'),
html.Div(id='dummy2')
])
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon1-state', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['transition']
else:
return 6
if __name__ == '__main__':
app.run_server(debug=True)
你可以这样做:
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def monster_1_callback(*args, **kwargs):
return update_health(*args, **kwargs)
@app.callback(
Output('rp-mon2-health', 'value'),
[Input('rp-mon2-name', 'value')]
)
def monster_2_callback(*args, **kwargs):
return update_health(*args, **kwargs)
@app.callback(
Output('rp-mon3-health', 'value'),
[Input('rp-mon3-name', 'value')]
)
def monster_3_callback(*args, **kwargs):
return update_health(*args, **kwargs)
现在包含逻辑的函数只写了一次,其他函数都是简单的传递,您永远不需要更新。
我遇到了完全相同的问题。仅输入和输出 ID 不同的大量回调。以下对我有用(我将从我的代码中提供一个示例,但想法是一样的)
def rangeslider_tocalendar(output, input):
@app.callback([Output(output, 'start_date'),
Output(output, 'end_date')],
[Input(input, 'value')])
def repeated_callback(range_slider):
cal_start = datetime.date.fromordinal(range_slider[0])
cal_end = datetime.date.fromordinal(range_slider[1])
return cal_start, cal_end
rangeslider_tocalendar('date-range', 'range-slider')
我将重复回调包装在一个函数中 rangeslider_tocalendar()
。然后我只调用包装函数并传入输入和输出 id。让意大利面远离我的盘子。
逻辑上等效的方法,但 重复代码较少,将在循环中分配回调,
def update_health(monster):
if not monster:
return 11
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
for i in range(1, 13):
app.callback(Output(f'rp-mon{i}-health', 'value'),
[Input(f'rp-mon{i}-name', 'value')])(update_health)
一个更规范的选择是使用 Dash 的 pattern-matching callback 功能,
@app.callback(Output(dict(id=MATCH, type='rp-mon-health'), 'value'),
[Input(dict(id=MATCH, type='rp-mon-name'), 'value')])
def monster_callback(monster):
if not monster:
return 11
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
除了清晰简洁的语法外,这种方法还具有扩展到 动态 组件数量的优势。也就是说,如果您在运行时 add/remove input/output 组件对(例如,由于其他一些用户选择,您想添加另一个选项),模式匹配方法仍然有效。固定的回调分配显然不会。