Блокировка потока pandas apply в Textual-приложении

Разрабатываю консольное приложение на основе библиотеки Textual. В приложении есть обработка табличных данных с использованием pandas. Данная обработка может занимать продолжительное время. Чтобы пользователь не счел, что приложение зависло, использую виджет индикатора загрузки. Проблема в том, что, когда начинается работать pandas, то индикатор загрузки не отображается, пока идет обработка табличных данных с использованием pandas, т.е., как я понимаю, pandas блокирует основной поток приложения. Вот пример кода, демонстрирующего данную ситуацию:

from textual import work
from textual.app import App, ComposeResult
from textual.widgets import Static
import pandas as pd



class DataApp(App):

    def compose(self) -> ComposeResult:
        self.widg = Static('Операция завершена')
        yield self.widg

    def on_mount(self) -> None:
        self.load_data()

    @work
    async def load_data(self) -> None:
        # Уведомляем о начале загрузки
        self.widg.loading = True

        # Выполняем длительную операцию
        df = pd.DataFrame({'a': [i for i in range(100000)]})
        df.apply(lambda x: x**2)
        df.to_excel('1.xlsx')

        # Уведомляем о завершении загрузки
        self.widg.loading = False
if __name__ == "__main__":
    app = DataApp()
    app.run()

Возможно ли сделать так, чтобы индикатор загрузки отображался, пока исполнятся код с pandas?

Спасибо, добавление @work(thread=True) помогло и в принципе меня устраивает результат. Но столкнулся еще с интересным моментом. Вот обновленный код (после запуска приложения не забудьте нажать s):

import numpy as np
import pandas as pd
from textual import work
from textual.app import App
from textual.widgets import Static, ProgressBar, LoadingIndicator
from asyncio import sleep




class ProgressBarApp(App):
    CSS = """
    LoadingIndicator {
        height: 1fr;
    }
    Static {
        height: 1fr;
    }
    ProgressBar {
        height: 1fr;
    }
    """
    BINDINGS = [("s", "start", "Start")]
    def __init__(self):
        super().__init__()
        self.progress_widget = Static("Progress: 0%")
        self.total = 100000  # Общее количество операций
        self.current = 0  # Текущий прогресс

    def compose(self):
        yield LoadingIndicator()
        yield ProgressBar()
        yield self.progress_widget

    def on_mount(self) -> None:
        self.query_one(LoadingIndicator).visible = False

    def update_progress(self, row):
        percentage = (row.name / self.total) * 100
        self.query_one(ProgressBar).advance(percentage)
        self.progress_widget.update(f"Progress: {percentage:.2f}%")
        return row ** 2

    @work(thread=True)
    async def run_process(self):
        self.query_one(LoadingIndicator).visible = True
        self.query_one(ProgressBar).update(total=100)
        self.progress_widget.update("Начинаем обработку...")
        await sleep(2)
        df = pd.DataFrame(np.random.randint(0, 100, (self.total, 6)))
        df.apply(self.update_progress, axis=1)
        self.progress_widget.update("Сохраняем файл...")
        df.to_excel('1.xlsx')
        self.progress_widget.update("Конец!!!")
        self.query_one(LoadingIndicator).visible = False


    def action_start(self):
        self.run_process()

if __name__ == "__main__":
    ProgressBarApp().run()

В этом примере два индикатора выполнения программы: от Textual (графический, красивый) и текстовый, указывающий процент выполнения. Так вот индикатор от Textual заполняется сильно быстрее, чем текстовый, хотя я ожидаю, что они должны работать синхронно.


Ответы (1 шт):

Автор решения: kaetosh

Спасибо, добавление @work(thread=True) помогло и в принципе меня устраивает результат. Но столкнулся еще с интересным моментом. Вот обновленный код (после запуска приложения не забудьте нажать s):

import numpy as np
import pandas as pd
from textual import work
from textual.app import App
from textual.widgets import Static, ProgressBar, LoadingIndicator
from asyncio import sleep




class ProgressBarApp(App):
    CSS = """
    LoadingIndicator {
        height: 1fr;
    }
    Static {
        height: 1fr;
    }
    ProgressBar {
        height: 1fr;
    }
    """
    BINDINGS = [("s", "start", "Start")]
    def __init__(self):
        super().__init__()
        self.progress_widget = Static("Progress: 0%")
        self.total = 100000  # Общее количество операций
        self.current = 0  # Текущий прогресс

    def compose(self):
        yield LoadingIndicator()
        yield ProgressBar()
        yield self.progress_widget

    def on_mount(self) -> None:
        self.query_one(LoadingIndicator).visible = False

    def update_progress(self, row):
        percentage = (row.name / self.total) * 100
        self.query_one(ProgressBar).advance(percentage)
        self.progress_widget.update(f"Progress: {percentage:.2f}%")
        return row ** 2

    @work(thread=True)
    async def run_process(self):
        self.query_one(LoadingIndicator).visible = True
        self.query_one(ProgressBar).update(total=100)
        self.progress_widget.update("Начинаем обработку...")
        await sleep(2)
        df = pd.DataFrame(np.random.randint(0, 100, (self.total, 6)))
        df.apply(self.update_progress, axis=1)
        self.progress_widget.update("Сохраняем файл...")
        df.to_excel('1.xlsx')
        self.progress_widget.update("Конец!!!")
        self.query_one(LoadingIndicator).visible = False


    def action_start(self):
        self.run_process()

if __name__ == "__main__":
    ProgressBarApp().run()

В этом примере два индикатора выполнения программы: от Textual (графический, красивый) и текстовый, указывающий процент выполнения. Так вот индикатор от Textual заполняется сильно быстрее, чем текстовый, хотя я ожидаю, что они должны работать синхронно.

→ Ссылка