session_state is a dictionary storing your values, hence you need keys to access the items in a dictionary.
Two things to note about this:
- This is not normal for a dictionary (because
session_state is actually a dataclass wrapped around a dictionary), but
session_state already provides access via the
. notation, so you can access the key
- The attributes for your
SessionStateAutoClass instance are also stored a dictionary -
So as far as the keys go, your class wrapper does not actually change anything. You still need the attribute name (key) to access the value, only instead of
st.session_state.count you write
ssa.count (if you want less typing, you can just alias
session_state on import).
checking if keys exist
A key needs to exist in the dictionary if you want to access it. You could:
- Always assign the key to a default value first thing in your script
- Check if a key exists before reading/writing to it
- Wrap your access/assignment/update to the dictionary in
#1 would be nonsensical in the context of
session_state. Why? Because
session_state exists for you to preserve your variables between re-runs. If you always clear it, it will work like any other variable in the script (remember, streamlit re-runs the script on every interaction and normal variables are not stored, only
#2 and #3 are mechanistically different, but functionally equivalent ways of doing the same thing - in #2 you “look before you leap”, in #3 you “ask for forgiveness”.
Note that this does not apply just to dictionary keys, but to variables, attributes et cetera. Consider:
>>> class Foo():
... def __init__(self):
... self.bar = 42
>>> a = Foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'baz'. Did you mean: 'bar'?
Anything in Python will throw an error if you reference it before assignment. You could, of course, construct a class which returns
None if the attribute does not exist–as you did. This might make sense in some applications, but in Python, “explicit is better than implicit”. Having your application throw an exception if you try to access an object which does not exist is preferable to it silently returning
None. In your implementation, you get to ignore the exception but then you still need to keep checking whether something is
None or not, and if you forget, bad things might happen, and they might happen silently (for example if the value is used in a condition, as you already pointed out).
adding update functions, and on_clicks
First of all,
on_click can call any function, not just those updating session_state. So the primary purpose of
on_change callbacks is just to have something–anything–happen when you change a widget value.
Second, a callback can be used to immediately change the associated state without letting the script run until the end. So yes, changing a
session_state value and calling an
experimental_rerun() after a button press could be considered functionally equivalent to using a callback with a function changing the same value. But if you do this, and you want to abstract this code, you might want to put it inside in a function, and then instead of writing
if st.button("A button"):
You might want to write
st.button("Button", on_click=my_function). This looks better, is easier to understand and pre-dates (I think)
experimental_rerun, which is still experimental and could be removed.
The part which you find confusing (I think?) is where some people update
session_state from a widget, in a callback from that widget. This is caused by a particular quirk of Streamlit, which really keeps track of two different session states:
- one is the user
session_state where you explicitly save values
- the second allows you to save widget state to
session_state by declaring the widget with a
key parameter, it’s just syntactic sugar
The two are indistinguishable for most purposes, unless your widget vanishes at any point. The values you explicitly save to
session_state are always preserved until they are explicitly removed or updated. If you declare a widget with a
key parameter, the widget’s value is saved to
session_state, but it’s removed if the widget is not rendered for some reason (pages, conditional widgets, etc.). Hence, some people will ignore the
key parameter and have callbacks which explicitly save widget state to
session_state, so that it persists. If your widgets always get rendered, you don’t have to update in callbacks, just put in the
key parameter and the widget value will be saved in session_state automatically, like:
tell me what I’m missing here and why this is bad.
Mostly because if you wrap an API you do not fully understand, in a language you do not know very well, inside a wrapper class which changes its behavior, you might have a bad time when things go wrong. Streamlit API can be confusing at times, the last thing you want is to add another layer of confusion on top of it while you are figuring out how it works.