1. Understand Streamlit’s Rerun philosophy. Any user interaction with app widgets (e.g., selectboxes, text input areas, etc.) triggers a rerun. Learn to “tame” rerun and turn it into an advantage by making use of function callbacks and proper design of app flow.
  2. Because of Streamlit’s rerun nature, app flow could get complicated and repetitive because you have to cover all edge cases and flow paths, resulting in rewriting some parts just for slightly different flow paths. Make heavy use of functions to simplify this process.
  3. Although Streamlit reruns the app to help you treat it as a script that runs from top to bottom, always try to manually catch exceptions before they happen, even if they’re down in the app and after st.stop() guards. You never know if you want to restructure your app in the future, so it’s better to write every line as if it could be the first line of the script.
  4. Stremalit in particular, and Python in general, don’t have a decent way to handle nulls and Nones. You end up writing a lot of if-else conditions before changing any Streamlit global variable. If you don’t do it, Streamlit is not smart enough to create global variables for you. For example, this raises an exception:
import streamlit as st
st.session_state.model = 'gpt-4'

But this works fine:

import streamlit as st
if 'model' not in st.session_state:
	st.session_state.model = 'gpt-4'
	# or st.session_state['model'] = 'gpt-4'

Streamlit supports get like so:

import streamlit as st
model = st.session_state.get('model', 'gpt-4')

But get does NOT update its object—it only returns a default value in case the object doesn’t have the said attribute (e.g., model). This means the following now raises an error:

import streamlit as st
model = st.session_state.get('model', 'gpt-4')
st.session_state.model = 'gpt-3.5-turbo'

I found it useful to write my own version of get like this:

def st_get(value: str, default: Any, where=st.session_state):
    if hasattr(where, value):
        return getattr(where, value)
    else:
        setattr(where, value, default)
        return getattr(where, value)

Now if I use it like this, it will also update st.session_state:

model = st_get('model', 'gpt-4')
st.session_state.model
#> 'gpt-4'