简单的问题是,如何实例化任意一组服务器模块?我的 UI 模块按预期呈现,但我看到有迹象表明按钮每次单击都会触发多个服务器模块响应(不增加按钮值)或根本不触发服务器模块。
我正在寻找有关如何做得更好或我可能做错了什么的建议。
目标是创建一个根据外部文件内容进行更新的 UI(本质上是每行一个新的折叠面板)。服务器模块还可以清除该文件中的行(以及 UI 中的元素),使其成为循环。我读取了文件的内容(由
reactive.file_reader()
监控),并循环遍历各行以实例化新的服务器和 UI 模块。每次更新外部文件时都会运行循环。
每次文件更新时,我可能会创建多个相同的模块实例,但我不能确定。 UI 根据需要呈现和导航。单击“停止运行”按钮后,查看多个手风琴元素和模式对话框。实验第一次在会话中结束时,外部文件和 UI 将按预期更新。第二次创建两个对话框:第一个仅注册“保持实验运行”按钮,第二个对话框接受任一按钮并在 UI 中提供适当的响应。所有后续“结束实验”的尝试都无法在服务器中做出任何响应。有关以下症状的更多详细信息。
代码如下所示:
@reactive.file_reader(watched_file):
def data():
do_stuff
return list_of_desired_modules
@reactive.effect
@reactive.event(data)
@def _():
server_list=[]
ui_list = []
for item in data():
server_list.append(server_module_instance(item, args....) #server logic to populate graph,etc in UI
ui_list.append(ui_module_instance(item, args....) #returns accordion_panel object
@output
@render.ui
def dynamic_ui():
return ui.accordion(*ui_list, id ="dynamic_accordion")
return server_list
我检查了浏览器中的元素,以确认所有按钮都具有从其模块实例继承的唯一名称。
我策略性地放置了
print
命令来报告单击按钮时发生的情况。
@reactive.Effect
@reactive.event(input.stop_run)
def _():
print("stop_run registered", input.stop_run() )
confirm_stop = ui.modal("Are you sure you want to stop this run?",
title = "Stop Run?",
footer= ui.row(ui.column(6,ui.modal_button("Keep Experiment Running")),
ui.column(6,ui.input_action_button("commit_stop", "End the Experiment"))),
easy_close= False,
)
ui.modal_show(confirm_stop
@reactive.Effect
@reactive.event(input.commit_stop)
def _():
print(input.commit_stop(), "commit stop value")
... a bunch of other stuff, including removing a record from the watched_file...
对于我尝试停止多次运行的新会话,我得到以下打印结果:
#click stop_run on a module/accordion
stop_run registered 1 #one click, one increment, one print
1 commit stop value #modal dialog and everything works
...
#click stop on another module/accordion
stop_run registered 1 #one click, one increment, two prints
stop_run registered 1
1 commit stop value #two modal dialogs show up,
#first only accepts the modal_button,
#second accepts action button
...
#click stop on another module/accordion
stop_run registered 1 #once click, one increment, three prints
stop_run registered 1
stop_run registered 1
#one modal shows, action button is unresponsive,
#modal button closes modal dialog
...
#click again on same module/accordion
stop_run registered 2 #one click, one increment, three prints, same behavior as previous
stop_run registered 2
stop_run registered 2
我尝试使用
ui.remove_ui()
删除可疑的预先存在的模块(在循环内,在 UI 和服务器列表中返回新副本之前),但这会产生一个没有任何 UI 的空白空间。
我已经研究了
ui.insert_accordion()
和 ui.remove_accordion()
,但它们没有我需要的图形、模式对话框和按钮的服务器逻辑,如链接图像所示。
shiny for python 网页上的所有动态 UI 或模块示例都有硬编码的模块数量,这些模块要么显示,要么隐藏。不是动态创建模块调用的示例。
TLDR:在创建新的服务器/ui 模块 ID 时添加一个计数器。每次执行循环时递增。希望这是可以推广的。
一开始的故障排除尝试最终修复了该行为。由于每次运行带有服务器/ui 列表循环的块时,问题似乎都会增加,因此我决定向模块 ID 添加一个计数器。这样,当我检查浏览器中的元素时,我可以判断我是在单击模块的第 0 个实例还是第 N 个实例。这些元素通常是第 N 个元素,但行为是所需的:一次单击、一次打印、一次成功完成下游任务、没有无响应的按钮。
代码如下:
counter = reactive.value(0) #NEW CHANGE: counter = 0 at start of new session
@reactive.file_reader(watched_file):
def data():
do_stuff
return list_of_desired_modules
@reactive.effect
@reactive.event(data)
@def _():
server_list=[]
ui_list = []
for item in data():
server_list.append(server_module_instance(f"{item}_{counter()}", args....) # NEW CHANGE: changed ID to include counter, new ID for same item
ui_list.append(ui_module_instance(f"{item}_{counter()}", args....) #NEW CHANGE: changed ID to include counter, new ID for same item
counter.set(counter() + 1) #NEW CHANGE:counter increments with every loop
@output
@render.ui
def dynamic_ui():
return ui.accordion(*ui_list, id ="dynamic_accordion")
return server_list
内存使用量似乎并没有因为后台大量的先前迭代而激增,因此这个解决方案似乎令人满意。希望它们被清理干净。