Понимание потоков данных Streamlit и как отправить форму последовательным образом

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

Основная концепция

В списке хранится 3 фрейма данных, а форма на боковой панели показывает supplier_name и po_number из соответствующего фрейма данных. Когда пользователь нажимает кнопку Next, информация внутри supplier_name и po_number text_input будет сохранена (в этом примере они в основном распечатываются в верхней части боковой панели).

Проблема

Это приложение хорошо работает, когда пользователь ничего не меняет внутри text_input, но если пользователь что-то меняет, приложение ломается. Например, см. рисунок ниже, когда я меняю po_number на somethingrandom, сохраненная информация не somethingrandom, а p123 из первого кадра данных.

Более того, если информация из следующего фрейма данных такая же, как и в первом фрейме данных, измененное значение внутри text_input не изменится для следующего отображения. Например, поскольку имя поставщика первого и второго фрейма данных равно S1, если я изменю имя поставщика на S10, а затем нажму «Далее», supplier_name все еще будет S10 во втором фрейме данных, а имя поставщика второго фрейма данных должно быть S1. Но если имя поставщика для следующего фрейма данных изменилось, информация внутри text_input будет изменена.

Обоснование

Если вы изо всех сил пытаетесь понять, почему я хочу это сделать, первоначальное использование для этого заключается в том, чтобы область ввода боковой панели извлекала информацию из каждого PDF-файла, а затем, когда пользователь подтверждает, что вся информация верна, он нажимает «Далее», чтобы просмотреть следующий PDF-файл. . Но если что-то не так, они могут изменить информацию внутри text_input, затем нажать «Далее», и информация об измененном значении будет записана, а для следующего pdf извлеченная информация должна отражать то, что представляет собой следующий pdf. Я сделал это в Rshining довольно просто, но не могу понять, как здесь работает поток данных в Streamlit, пожалуйста, помогите.

Воспроизводимый пример

import streamlit as st
import pandas as pd

# 3 dataframes that are stored in a list
data1 = {
    "supplier_name": ["S1"],
    "po_number": ["P123"],
}
data2 = {
    "supplier_name": ["S1"],
    "po_number": ["P124"],
}
data3 = {
    "supplier_name": ["S2"],
    "po_number": ["P125"],
}
df1 = pd.DataFrame(data1)
df2 = pd.DataFrame(data2)
df3 = pd.DataFrame(data3)

list1 = [df1, df2, df3]

# initiate a page session state, every time next button is clicked
# it will go to the next dataframe in the list
if 'page' not in st.session_state:
    st.session_state.page = 0

def next_page():
    st.sidebar.write(f"Submitted! supplier_name: {supplier_name} po_number: {po_number}")
    st.session_state.page += 1

supplier_name_value = list1[st.session_state.page]["supplier_name"][0]
po_number_value = list1[st.session_state.page]["po_number"][0]

# main area
list1[st.session_state.page]

# sidebar form

with st.sidebar.form("form"):
   supplier_name = st.text_input(label = "Supplier Name", value=supplier_name_value)
   po_number = st.text_input(label = "PO Number", value=po_number_value)
   next_button = st.form_submit_button("Next", on_click=next_page)

Ожидаемое поведение

Информация о кадре данных извлекается в область ввода боковой панели. Пользователь может изменить ввод, если пожелает, затем нажать «Далее», и значения внутри областей ввода будут сохранены. Когда он переходит к следующему фрейму данных, значения внутри текстового ввода будут обновлены для извлечения из следующего фрейма данных и повторяются.

Потяните за рычаг выброса энергососущих проектов
Потяните за рычаг выброса энергососущих проектов
На этой неделе моя команда отменила проект, над которым я работал. Неделя усилий пошла насмарку.
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
Учебник по веб-скрапингу
Учебник по веб-скрапингу
Привет, ребята... В этот раз мы поговорим о веб-скрейпинге. Целью этого обсуждения будет узнать и понять, что такое веб-скрейпинг, а также узнать, как...
1
0
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я не уверен, что полностью понимаю, чего вы пытаетесь достичь, но похоже, что вы никогда не обновляете имя поставщика в list1 после того, как пользователь обновляет имя через виджет ввода текста.

Ответ принят как подходящий

Я не совсем уверен, что вы собираетесь делать, но после некоторого возни единственный способ, которым я смог добиться такой последовательной обработки отправки формы, — это st.experimental_rerun(). Я ненавижу прибегать к этому, так как он может быть удален в любое время, так что, надеюсь, есть лучший способ.

Без experimental_rerun() формы требуют двух отправок для фактического обновления состояния. Мне не удалось найти «правильный» способ добиться немедленного обновления для поддержки ожидаемого поведения.

Вот моя попытка:

import pandas as pd  # 1.5.1
import streamlit as st  # 1.18.1


def initialize_state():
    data = [
        {
            "supplier_name": ["S1"],
            "po_number": ["P123"],
        },
        {
            "supplier_name": ["S1"],
            "po_number": ["P124"],
        },
        {
            "supplier_name": ["S2"],
            "po_number": ["P125"],
        },
    ]
    state.dfs = state.get("dfs", [pd.DataFrame(x) for x in data])
    first_vals = [{x: df[x][0] for x in df.columns} for df in state.dfs]
    state.selections = state.get("selections", first_vals)
    state.pages_expanded = state.get("pages_expanded", 0)
    state.current_page = state.get("current_page", 0)
    state.just_modified_page = state.get("just_modified_page", -1)


def handle_submit(i):
    st.session_state.selections[i] = {
        "supplier_name": state.new_supplier_name,
        "po_number": state.new_po_number,
    }
    state.current_page = i
    state.just_modified_page = i

    if i < len(state.dfs) - 1 and state.pages_expanded == i:
        state.pages_expanded += 1

    st.experimental_rerun()


def render_form(i):
    with st.sidebar.form(key=f"form-{i}"):
        supplier_name = state.selections[i]["supplier_name"]
        po_number = state.selections[i]["po_number"]

        if i == state.just_modified_page:
            st.sidebar.write(
                f"Submitted! supplier_name: {supplier_name} "
                f"po_number: {po_number}"
            )
            state.just_modified_page = -1

        state.new_supplier_name = st.text_input(
            label = "Supplier Name",
            value=supplier_name,
        )
        state.new_po_number = st.text_input(
            label = "PO Number",
            value=po_number,
        )

        if st.form_submit_button("Next"):
            handle_submit(i)


state = st.session_state
initialize_state()

for i in range(state.pages_expanded + 1):
    render_form(i)

# debug
st.write("state.pages_expanded", state.pages_expanded)
st.write("state.current_page", state.current_page)
st.write("state.just_modified_page", state.just_modified_page)
st.write("state.dfs[state.current_page]", state.dfs[state.current_page])
st.write("state.selections", state.selections)

Я предполагаю, что вы хотите отслеживать выбор пользователя, но не изменять фреймы данных. Если вы хотите изменить кадры данных, это проще: замените state.selections фактическими записями в dfs по индексу и столбцу:

# ...
def handle_submit(i):
    st.session_state.dfs[i]["supplier_name"] = state.new_supplier_name,
    st.session_state.dfs[i]["po_number"] = state.new_po_number,
    #st.session_state.selections[i] = {
    #    "supplier_name": state.new_supplier_name,
    #    "po_number": state.new_po_number,
    #}

# ...

def render_form(i):
    with st.sidebar.form(key=f"form-{i}"):
        supplier_name = state.dfs[i]["supplier_name"][0]
        po_number = state.dfs[i]["po_number"][0]
        #supplier_name = state.selections[i]["supplier_name"]
        #po_number = state.selections[i]["po_number"]
# ...

Теперь можно сделать это на 100% динамическим, но я жестко запрограммировал supplier_name и po_number, чтобы избежать преждевременного обобщения, которое может вам не понадобиться. Если вы хотите обобщить, используйте df.columns так же, как initialize_state во всем коде.

Спасибо за ответ. Несколько дней назад я также нашел Experiment_run, и вы правы, это функция, которую потенциально можно удалить. И я совершенно сбит с толку тем, почему для извлечения информации нужно дважды щелкнуть кнопку, поскольку приложение не обновлялось сверху вниз при нажатии кнопки. Один из способов обойти experiemnt_run — использовать on_click, а затем назначить ключ виджетам, я еще не пробовал, обновлю здесь позже.

Subaru Spirit 17.02.2023 14:42

Цель состоит в том, чтобы сначала прочитать пакет документов, и все эти документы (представьте, что каждый документ является фреймом данных) хранятся в списке. Затем боковая панель читает то, что находится внутри каждого документа, один за другим, но если что-то выглядит не так, пользователь может исправить боковую панель. После того, как пользователь нажмет «Далее», эта информация будет сохранена в электронной таблице Excel, и пользователь перейдет к следующему документу в списке. Фактический документ не будет изменен, но эта информация будет сохранена. Надеюсь, теперь это имеет больше смысла.

Subaru Spirit 17.02.2023 14:49

Спасибо за ответ. Я пытался использовать обработчики on_click, но не смог заставить его работать, поэтому прибегнул к experimental_rerun(). Я также хотел бы вернуться к этому, когда смогу, и попытаться заставить его работать с обратными вызовами, но не стесняйтесь оставлять комментарий и ответ, если вы опередите меня.

ggorlen 19.02.2023 01:50

Другие вопросы по теме

ValueError: истинное значение индекса неоднозначно. Используйте a.empty, a.bool(), a.item(), a.any()
Динамический пользовательский интерфейс Streamlit для создания динамических виджетов ввода в зависимости от значения из другого виджета ввода.
Пользовательский домен Google App Engine не перенаправляет на https
Как округлить десятичные дроби при записи фрейма данных в потоке
Кнопка загрузки Streamlit не работает при попытке загрузить файлы в виде zip
Streamlit на AWS: бессерверные варианты?
Не удается заставить stqdm() работать в цикле for в приложении с потоковой подсветкой
Как создать несколько числовых входов и обновить фреймворк данных, когда появится новая запись, используя потоковый python?
Обновление данных в реальном времени для потоковой передачи из переменной python
Streamlit, как использовать состояние сеанса с Aggrid, чтобы сохранить выбор даже после переключения страниц?