Блокировка потока 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 шт):
Спасибо, добавление @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 заполняется сильно быстрее, чем текстовый, хотя я ожидаю, что они должны работать синхронно.