Rozdział 23 · Aneks

Projekt domowy A: kalkulator z Tkinter

Ekran, siatka przycisków i bezpieczne przetwarzanie wyrażenia przez ast – bez żadnego wywołania eval().

Klasyczny kalkulator na Tkinter: ekran u góry, siatka przycisków z cyframi i znakami na dole — ten sam zabieg, co w symulatorze „Kółko-krzyżyk” z rozdziału 19, jedna funkcja dla wszystkich przycisków:

calculator.py
KNOPKI = [
    ("7", 1, 0), ("8", 1, 1), ("9", 1, 2), ("/", 1, 3),
    ("4", 2, 0), ("5", 2, 1), ("6", 2, 2), ("*", 2, 3),
    # ...
]

for podpis, stroka, stolbec in KNOPKI:
    knopka = tk.Button(root, text=podpis,
                        command=lambda s=podpis: na_cifru_ili_znak_nazhali(s))
    knopka.grid(row=stroka, column=stolbec, sticky="we")

Ocena wyrażenia bez eval()

Normalne eval() wykonuje każdy przekazany mu kod Python — Nawet jeśli łańcuch znaków formalnie przypomina arytmetykę, ogranicz się do bezpiecznego typowania operacje są bardziej złożone, niż się wydaje. Kalkulator analizuje wyrażenie przez ast i oblicza tylko to, co jest faktycznie dozwolone: liczby, + - * / i nawiasy:

calculator.py
def vychislit_uzel(uzel: ast.AST) -> float:
    if isinstance(uzel, ast.Constant) and isinstance(uzel.value, int | float):
        return uzel.value
    if isinstance(uzel, ast.BinOp) and type(uzel.op) in DOPUSTIMYE_OPERATORY:
        return DOPUSTIMYE_OPERATORY[type(uzel.op)](
            vychislit_uzel(uzel.left), vychislit_uzel(uzel.right)
        )
    if isinstance(uzel, ast.UnaryOp) and isinstance(uzel.op, ast.USub):
        return -vychislit_uzel(uzel.operand)
    raise ValueError(f"węzeł nieprawidłowego wyrażenia: {ast.dump(uzel)}")

def vychislit_vyrazhenie(vyrazhenie: str) -> str:
    derevo = ast.parse(vyrazhenie, mode="eval")
    return str(vychislit_uzel(derevo.body))
Dlaczego to jest bezpieczniejsze niż eval(), nawet ograniczone
Linia jak "import os" nie jest poprawnym wyrażeniem arytmetycznym, więc ast.parse(..., mode="eval") natychmiast wywołuje SyntaxError – po prostu nie ma gdzie pojawić się analiza. Drewno vychislit_uzel() rozumie tylko liczby i cztery operatory arytmetyczne: wywołanie funkcji, odwołanie do zmiennej lub wykonanie dowolnego innego kodu przez niego jest zasadniczo niemożliwe, a nie zabronione przez sprawdzanie znaków.

Logika oddzielna od interfejsu

Stan kalkulatora — aktualnie wprowadzone wyrażenie — zwykła łańcuch znaków wewnątrz małej klasy bez żadnego odniesienia do Tkinter. Import pliku calculator.py nie otwiera okna: tk.Tk() jest tworzony Tylko w środku main(), więc logika może być sprawdzana przez testy bez żadnego interfejsu graficznego.

Pełny kod: projects/tkinter/calculator/calculator.py

Quest

★★ Zadanie samodzielne
± przycisk

Dodaj przycisk, który zmienia znak aktualnego wyniku bez dotykania funkcji vychislit_vyrazhenie().

GitHubPraktyka GitHub

Utwórz Issue „Add calculator project” z kryteriami gotowości (interfejs otwiera się, arytmetyka działa, dzielenie przez zero jest obsługiwane, testy przechodzą), utwórz gałąź feat/calculator, dodaj projekt do python-mini-projects i ukończyć pracę w Pull Request.

Praktyka: obliczenia i sekwencje kliknięć
Moduł tkinter otwiera natywne okno Python — wykonaj lokalnie w VS Code, PyCharm lub Jupyter
Praktyka kursuje lokalnie
Otwórz praktykę →