2025, Dec 05 09:02
Вкладка из отдельного файла в Shiny for Python: как избежать пустой панели с expressify
Разбираем, почему вкладка из другого файла в Shiny for Python оказывается пустой, и как исправить с помощью expressify: подключение nav_panel в navset_tab.
Когда приложение Shiny for Python разрастается, логично вынести части интерфейса в отдельные модули. Частый сценарий — вкладка навигации, которую хочется определить в другом файле и подключить в navset_tab основного приложения. Если просто перенести код как есть, нередко получите пустую вкладку. Ниже — почему так происходит и как сделать правильно.
Как воспроизвести проблему
Представим основное приложение с navset_tab и отдельный файл, который пытается добавить дополнительную вкладку через обычную функцию. Функция вызывается внутри набора вкладок, но панель оказывается пустой.
app.py
import page as sections
from shiny.express import input, ui
with ui.navset_tab(id="navset_active_tab"):
with ui.nav_panel("Welcome", value="home_view"):
with ui.card():
ui.input_selectize(
"dummy_select", "Filler",
["list", "of", "items", "here"],
multiple=False
)
sections.extra_tab()
page.py
from shiny import render
from shiny.express import ui
def extra_tab():
with ui.nav_panel("Page I want to put in the main app", value="aux_view"):
@render.express
def text_stub():
"Filler text"
Что на самом деле происходит
Если обернуть инструкции построения интерфейса в обычную функцию и вызвать её внутри вашего navset_tab, будет использовано только возвращаемое значение функции. Поскольку функция ничего не возвращает, фактическое значение — None, и добавленная вкладка остаётся пустой. Иными словами, интерфейс, который вы «собирали» построчно внутри функции, не возвращается; возвращается лишь итоговое значение функции — не то, что вам нужно.
Как исправить
Используйте express.expressify, чтобы функция вела себя как код Shiny Express, который «излучает» UI по мере выполнения. Тогда вызов функции создаёт именно тот интерфейс, который задан её строками, а не только её возвращаемым значением.
page.py
from shiny import render
from shiny.express import ui, expressify
@expressify
def extra_tab():
with ui.nav_panel("Page I want to put in the main app", value="aux_view"):
@render.express
def text_stub():
"Filler text"
app.py
import page as sections
from shiny.express import input, ui
with ui.navset_tab(id="navset_active_tab"):
with ui.nav_panel("Welcome", value="home_view"):
with ui.card():
ui.input_selectize(
"dummy_select", "Filler",
["list", "of", "items", "here"],
multiple=False
)
sections.extra_tab()
Почему это важно
Разделение интерфейса по файлам помогает поддерживать крупные приложения, но у Shiny Express есть особая модель выполнения. Без expressify учитывается лишь возвращаемое значение функции, а не постепенный UI, который вы ожидаете «получать» из каждой строки. expressify согласует вызов функции с поведением Express «показывать по мере выполнения», поэтому модульные части корректно рендерятся в составе общего макета.
Что запомнить
Если нужно импортировать nav_panel (или любой другой Express UI) из отдельного файла в navset_tab, поместите UI в функцию и декорируйте её express.expressify. Вызовите эту функцию в том месте, где должен появиться интерфейс. Эта небольшая правка избавляет от пустых вкладок и сохраняет задуманное пошаговое отображение вашего Express UI.