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:
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:
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))
"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
Dodaj przycisk, który zmienia znak aktualnego wyniku bez dotykania funkcji vychislit_vyrazhenie().
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.