# Python 12 (streamlit)

# Python 12 (streamlit)
Photo by Shannon Potter / Unsplash

Problemy z bibliotekami GUI

  • nie zawsze wyglądają dobrze na każdym z systemów operacyjnych
  • czasami wymagają osobnego „dopieszczania” pod każdą z platform
  • potrafią być niekonsekwentne w budowie (np. patrz – left / right, n/e w TkInter)

Strony WWW?

  • w większości wypadków wyglądają bardzo podobnie niezależnie od systemu / przeglądarki
  • oparte o dość dobrze opisane standardy i technologie

Jest cała grupa aplikacji, które są tak naprawdę stroną www dostarczoną razem z bardzo „odchudzoną” przeglądarką np. Discord, Slack, WhatsApp, MS Teams, etc. Stąd warto poznać jakiś framework web-owy – jest szansa, że z jego pomocą stworzymy nie tylko stronę, ale również aplikację na komputery stacjonarne.

Streamlit

https://streamlit.io/

Biblioteka / framework stworzona z myślą o budowaniu stron do pracy z danymi, czy machine learning. Stąd też jego mocną stroną są różnej maści panele informacyjne. Umożliwia zbudowanie strony bez potrzeby napisania nawet linijki HTML-a / Javascriptu – wszystko przygotowujemy w Pythonie. Wystarczy zaimportować bibliotekę, skorzystać z gotowych komponentów i uruchomić aplikację poprzez streamlit run nazwa_pliku.py.

Alternatywa dla Streamlit, ale bardziej skomplikowana:

Streamlit

Krok 1. Instalacja w terminalu

pip install streamlit

Krok 2. Import biblioteki

import streamlit as st

Krok 3. Uruchamianie strony w terminalu:

streamlit run nazwa_pliku.py

Podstawowe komponenty

https://docs.streamlit.io/develop/api-reference

  • st.write("tekst")
  • st.title(), st.header(), st.subheader()
  • st.dataframe()
  • st.text_input()
  • st.line_chart(dataframe), st.bar_chart(dataframe)
  • st.button()
  • st.checkbox()
  • st.multiselect()
  • st.slider()
  • st.image()
  • st.balloons()

Komponenty "tekstowe"

st.write("tekst")

  • jeden z najbardziej uniwersalnych komponentów, umożliwia wyświetlanie tekstu, markdownu, ramek danych, obrazków

st.title("tekst")

  • umożliwia stworzenie tytułu (H1)

st.header("tekst")

  • nagłówek
  • można dodać parametr divider, który doda podkreślenie

st.subheader("tekst")

  • H3

Zarówno title, jak i subheader można w prosty sposób zastąpić stosowaniem markdowna w połączeniu z komponentem podstawowym st.write(" # Hello")

Komponenty do wprowadzania / wyboru danych

st.text_input()

  • pole do wprowadzania danych

st.slider()

  • suwak
  • możemy dodać min_value i max_value, które ograniczą nam zakres do wyboru

st.checkbox()

  • pole do zaznaczenia "ptaszkiem"

st.multiselect()

  • umożliwia wybrania kilka elementów z jakiegoś zakresu danych

Wykresy

st.bar_chart()

  • wykres słupkowy

st.line_chart()

  • wykres liniowy

st.scatter_chart()

  • wykres punktowy

Przydatne: https://matplotlib.org/

Matplotlib to biblioteka Pythona do tworzenia wizualizacji danych – wykresów, diagramów i grafik 2D. Jest jedną z najstarszych i najbardziej rozbudowanych bibliotek w tym obszarze, często używaną w połączeniu z NumPy i Pandas.
Najczęściej korzysta się z modułu matplotlib.pyplot, który działa podobnie do poleceń w MATLAB-ie.

Główne cechy Matplotlib
  • Różnorodność wykresów – obsługuje m.in. wykresy liniowe, słupkowe, kołowe, punktowe, histogramy, konturowe, trójwymiarowe.
  • Elastyczność – pozwala na pełną kontrolę nad wyglądem wykresu: kolorami, stylami linii, czcionkami, osiami, legendami.
  • Integracja – dobrze współpracuje z Pandas, NumPy, SciPy, scikit-learn.
  • Eksport – umożliwia zapisywanie wykresów w wielu formatach (PNG, SVG, PDF).
  • Interaktywność – w połączeniu z Jupyter Notebook można łatwo manipulować wykresami.

Ważne! Streamlit reruns your script from top to bottom every time you interact with your app. Each reruns takes place in a blank slate: no variables are shared between runs. Session State is a way to share variables between reruns, for each user session.

Myślimy o st.session_state jak o pudełku na dane dla tej jednej karty przeglądarki. Co do niego włożymy, nie znika przy klikaniu przycisków (Streamlit na każdą akcję „przelatuje” skrypt od góry, ale Twoje pudełko zostaje).

Przykład:

# Inicjalizacja

if 'key' not in st.session_state:
    st.session_state['key'] = 'value'

# Session State also supports attribute based syntax
if 'key' not in st.session_state:
    st.session_state.key = 'value'

# Reading
st.write(st.session_state.key)

# Update an item in Session State by assigning it a value:
st.session_state.key = 'value2'     # Attribute API
st.session_state['key'] = 'value2'  # Dictionary like API

# Delete a single key-value pair
del st.session_state[key]

# Delete all the items in Session state
for key in st.session_state.keys():
    del st.session_state[key]

# --------------------------------------------------------------------

st.text_input("Your name", key="name")

# This exists now:
st.session_state.name

Zadanie domowe 1.

Tworzyliśmy tekstową grę polegającą na zgadnięciu liczby z zakresu 1-100. Użytkownik w prostej wersji miał 10 podejść na zgadnięcie. Za każdym z nich dostawał informację czy poszukiwana liczba jest mniejsza, czy większa.
Spróbujcie odtworzyć tę grę w Streamlit-ie. Pamiętajcie o specjalnej zmiennej session_state do przechowywania stanu zmiennych (np. poszukiwanej liczby i pozostałych prób gracza):
https://docs.streamlit.io/develop/api-reference/caching-and-state/st.session_state
Możecie też pobawić się dodatkowymi komponentami np. warning, czy success do wyświetlania informacji użytkownikowi:
https://docs.streamlit.io/develop/api-reference/status/st.success
https://docs.streamlit.io/develop/api-reference/status/st.warning
Ciekawy mógłby być też komponent baloons uruchamiany w sytuacji gdy gracz zgadnie liczbę.
https://docs.streamlit.io/develop/api-reference/status/st.balloons

import streamlit as st
import random

proby_max = 10

st.set_page_config(page_title="Zgadnij liczbę", layout="wide")
st.write("# *Zgadnij liczbę!*")

# Inicjalizacja session_state
if "liczba_losowa" not in st.session_state:
    st.session_state.liczba_losowa = random.randint(1, 100)
    st.session_state.proby = 0
    st.session_state.komunikat = "Wybierz na suwaku liczbę z zakresu 1–100 i traf w losową liczbę!"
    st.session_state.gra_zakonczona = False

if "gracz" not in st.session_state:
    st.session_state.gracz = ""

if "strzal" not in st.session_state:
    st.session_state.strzal = 1

# Dane od gracza (zapisują się do session_state dzięki key=)
st.text_input("Wprowadź swoje imię lub pseudonim:", key="gracz")

if st.session_state.gracz:
    st.write(f"## Witaj w grze {st.session_state.gracz}!")
else:
    st.write("## Witaj w grze!")

# Slider tylko gdy gra nie jest zakończona
if not st.session_state.get("gra_zakonczona", False):
    st.slider("Wybierz liczbę w zakresie 1–100", min_value=1, max_value=100, key="strzal")
else:
    st.write("Koniec Gry. Rozpocznij nową grę, aby kontynuować.")

# Komponenty gry
col1, col2, col3 = st.columns(3)

with col1:
    # Sprawdź czy gra nie jest zakończona i czy nie przekroczono limitu prób
    if not st.session_state.get("gra_zakonczona", False) and st.session_state.proby < proby_max:
        if st.button("Sprawdź"):
            st.session_state.proby += 1
            liczba_gracza = int(st.session_state.strzal)
            liczba_randomowa = st.session_state.liczba_losowa
            
            if liczba_gracza < liczba_randomowa:
                st.session_state.komunikat = f"Za mało! (próba {st.session_state.proby}/{proby_max})"
            elif liczba_gracza > liczba_randomowa:
                st.session_state.komunikat = f"Za dużo! (próba {st.session_state.proby}/{proby_max})"
            else:
                st.session_state.komunikat = f"Trafione! Liczba to {liczba_randomowa}. Użyłeś {st.session_state.proby} prób!"
                st.session_state.gra_zakonczona = True
                st.balloons()
            
# Sprawdź czy wykorzystano wszystkie próby
            if st.session_state.proby >= proby_max and liczba_gracza != liczba_randomowa:
                st.session_state.komunikat = f"Wykorzystałeś wszystkie {proby_max} prób! Prawidłowa odpowiedź to {liczba_randomowa}."
                st.session_state.gra_zakonczona = True
    else:
        st.button("Sprawdź", disabled=True, help="Gra zakończona lub brak prób")

with col2:
    if st.button("Nowa gra"):
        st.session_state.liczba_losowa = random.randint(1, 100)
        st.session_state.proby = 0
        st.session_state.komunikat = "Nowa gra rozpoczęta — zgadnij liczbę z zakresu 1–100!"
        st.session_state.gra_zakonczona = False
        st.rerun()

with col3:
    if st.button("Pokaż odpowiedź"):
        st.session_state.komunikat = f"Odpowiedź: {st.session_state.liczba_losowa}"
        st.session_state.gra_zakonczona = True

st.info(st.session_state.komunikat)
st.metric("Liczba prób", st.session_state.proby)