2025, Dec 02 00:02

Как починить input в модуле Shiny for Python при expressify

Почему при expressify реактивный input в модуле Shiny for Python «теряется», и как это исправить: передать session input в модуль и использовать его в render.

Когда приложение Shiny for Python разрастается и выходит за рамки одного файла, разумно разбивать его на модули. Однако при использовании expressify и попытке обратиться к реактивным вводам из отдельного модуля часто появляется скрытая проблема: интерфейс отрисовывается, статический текст видно, а реактивные значения упорно не показываются. Суть в тонкости: объект input внутри модуля — это не тот же самый input, который привязан к текущей сессии.

Постановка проблемы

Приложение разделено на два файла. В главном описаны набор вкладок и базовый контрол, а во втором добавляется ещё одна вкладка с числовым полем и попыткой вывести его значение. Если вычислять или ссылаться на значение в той же функции, вывод остаётся пустым.

Ниже — минимальный пример, который воспроизводит проблему.

main_app.py

import section_view
from shiny import ui
from shiny.express import input, ui
with ui.navset_tab(id="active_tab"):
    with ui.nav_panel("Welcome", value="home_view"):
        with ui.card():
            ui.input_selectize(
                "dummy", "Filler",
                ["list", "of", "items", "here"],
                multiple=False
            )
    section_view.build_other()

section_view.py

from shiny import render
from shiny.express import input, ui, expressify
@expressify
def build_other():
    with ui.nav_panel("Detached page", value="aux_view"):
        ui.input_numeric("num_val", "I need this inpt value!", 1, min=1, max=1000)
        @render.express
        def show_text():
            val = input.num_val()
            val

Что здесь не так

Input, используемый внутри модуля, — отдельный объект, отличный от того, что живёт в главном приложении. В главном приложении уже есть session‑bound input, и именно он должен быть единственным источником истины. Повторный импорт input внутри модуля создаёт другой объект, о котором сессия не знает, поэтому выражения, зависящие от него, не дают результата. Это легко проверить: замените тело функции render на статическую строку — строка отобразится, а обращение к числовому значению — нет. В зависимости от того, как запускается код, это может даже проявиться как ошибка доступа к атрибуту у «не того» input.

Решение

Не импортируйте input в модуле. Вместо этого принимайте основной input параметром функции модуля и используйте его внутри блока render. Затем передайте session input из главного файла в модуль.

section_view.py

from shiny import render
from shiny.express import ui, expressify
@expressify
def build_other(inlet):
    with ui.nav_panel("Detached page", value="aux_view"):
        ui.input_numeric("num_val", "I need this input value!", 1, min=1, max=1000)
        @render.express
        def show_text():
            current = inlet.num_val()
            f"input value: {current}"

main_app.py

import section_view
from shiny import ui
from shiny.express import input, ui
with ui.navset_tab(id="active_tab"):
    with ui.nav_panel("Welcome", value="home_view"):
        with ui.card():
            ui.input_selectize(
                "dummy", "Filler",
                ["list", "of", "items", "here"],
                multiple=False
            )
    section_view.build_other(inlet=input)

Почему это важно

Когда вы делите приложение Shiny на несколько файлов, критично сохранять согласованность реактивных объектов, привязанных к сессии. Если модуль незаметно создаёт собственный input, он оказывается отделён от текущей сессии. UI отрисуется, статический текст появится, но реактивный вывод — нет, потому что чтение происходит из объекта, который сессия не обновляет. Передача session input в модуль сохраняет синхронизацию и позволяет пользоваться чистой, многофайловой структурой без компромиссов.

Итоги

Сохраняйте модульность, но направляйте корректный input в каждый модуль, которому он нужен. Не импортируйте новый input во вспомогательных файлах: принимайте его из главного приложения и используйте в render.express, чтобы отображать значения, задаваемые пользователем. Эта небольшая «проводка» предотвращает загадочные состояния без вывода и помогает вашему приложению Shiny for Python оставаться реактивным и удобным в сопровождении по мере роста.