Comments (4)
I renamed the issue from Improvements in JustPy to Callbacks vs Streamlit-Like "if"-statements for better clarification of the topic.
from nicegui.
Hi Eli,
Our problem with Streamlit was mainly due to the way it handles user interaction. Writing if st.button('Click'): st.text('Hey!')
is super cool. But it hides the underlying event loop by implicitly re-evaluating the script. That works in simple cases, but it gets quickly rather complicated to achieve apparently "normal" behavior.
State
If the script starts with loading some non-constant initial state, e.g. from an external source, this state is reset whenever the script is processed again. The following script already shows inconsistent behavior. Initializing state
with a constant works, but assigning a non-constant random value overwrites the user-selected state every time.
import streamlit as st
import random
state = random.randint(0, 5) # simulate external source
new_state = st.radio('State', ['A', 'B', 'C', 'D', 'E', 'F'], state)
st.text('State: ' + new_state)
It is hard to let Streamlit load the state once and let the user update it repeatedly. It gets worse when multiple widgets interact with each other, like a button resetting the radio selection to some value.
There are workarounds introducing session state. But they are not trivial and require quite some additional code. You can look into one of our projects to see how we mapped machine state onto session state to make it accessible for Streamlit:
https://github.com/zauberzeug/odrive-gui/blob/9d8a6d3b46a8e1f200670aa837bff476d98504a5/src/main.py#L21
Loops
Always reloading the script makes it also difficult to introduce own event loops, e.g. reading data and updating a plot repeatedly. In the above-mentioned project we found a workaround using threads that need to get stopped and restarted every time the script restarts due to user interaction:
https://github.com/zauberzeug/odrive-gui/blob/9d8a6d3b46a8e1f200670aa837bff476d98504a5/src/main.py#L40
Today (on the main branch) we avoid this struggle using NiceGUI:
https://github.com/zauberzeug/odrive-gui/blob/main/src/main.py
Handling user interaction without callbacks seems indeed intriguing. But we doubt it holds for slightly more demanding projects. Itβs just not like the language works: A (non-blocking) if-statement evaluates the conditional expression to decide which path to take, before the user gets the chance to interact. Doing it otherwise requires Streamlit to perform a significant amount of "magic", causing trouble in other respects.
from nicegui.
Thank you very much for the detailed answer Falko. It does indeed look that Streamlit has a serious design flaw. They tried making things very simple, but that ended limiting the utility of their framework.
from nicegui.
@elimintz I was curious to see whether I could come up with Streamlit-like if-statements for NiceGUI (or JustPy) UI elements that postpone code execution until an event occurs, but don't require re-running the whole script. Although I don't think it's super reliable and it could break in more complicated scenarios, it's interesting to see what is possible.
#!/usr/bin/env python3
from nicegui import ui
import inspect
import ast
def magic_button(text):
class NodeVisitor(ast.NodeVisitor):
def __init__(self, lineno):
self.lineno = lineno
self.node = None
def visit_If(self, node):
if node.lineno == self.lineno:
self.node = node
self.generic_visit(node)
frame = inspect.currentframe().f_back
with open(inspect.getsourcefile(frame)) as f:
code = f.read()
tree = ast.parse(code)
visitor = NodeVisitor(inspect.currentframe().f_back.f_lineno)
visitor.visit(tree)
block = code.splitlines()[visitor.node.lineno:visitor.node.end_lineno]
indentation = min(len(line) - len(line.lstrip()) for line in block)
block = '\n'.join(line[indentation:] for line in block)
ui.button(text, on_click=lambda _: exec(block, frame.f_globals, frame.f_locals))
ui.label('This script demonstrates postponed code execution with streamlit-like if-statements.')
count_label = ui.label('count = 0')
count = 0
if magic_button('Click me!'):
count += 1
print(f'count = {count}')
count_label.text = f'count = {count}'
When creating a "magic button", the source code is parsed into a tree and traversed looking for if-statements on the same line from which magic_button(...)
has been called. The body of the if-statement is extracted and assigned to the click handler of a normal ui.button(...)
, together with global and local variables of the corresponding execution frame.
The question remains, how "nice" and pythonic it is to bend the normal flow of code execution. As Streamlit shows, it's easy to find examples where such an approach leads to unexpected behavior.
from nicegui.
Related Issues (20)
- β οΈ `ui.upload` File upload stops at 99.9% HOT 1
- 3D visualization not reliable HOT 15
- From 188c81b on some pages load extremely slowly HOT 9
- I use pyinstaller to generate .exe file find question, how to solve it?
- Setting ui.number min/max property is failing after version nicegui-1.4.19 HOT 1
- When executing the run_method , a KeyError error occurs. HOT 6
- Native mode [Windows] : Stay on top option
- Firefox cannot establish the websocket connection but other browsers and firefox private browsing can. HOT 8
- app.storage.user in unit tests throws error. HOT 1
- confilict with multiprocessing when running in native mode? HOT 1
- Infinite scroll example errors HOT 5
- Nginx subpath example doesn't redirect correctly when navigating to subpage with trailing slash
- ui.plotly chart randomly collapses / shrinks HOT 2
- Leaflet always uses the full width
- Troubles while periodically updating the 3D scene HOT 6
- Native mode and resizability of the window HOT 4
- Text in input and select components is not aligned to each other HOT 5
- ui.input validation will remain space after it's value was corrected HOT 3
- Select fails to change value on first try. HOT 6
- `run_method` and `getElement` return inconsistent types of elements
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from nicegui.