|
| 1 | +"""Calculadora gráfica com avaliação aritmética segura.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import ast |
| 6 | +import math |
| 7 | +import operator |
1 | 8 | import tkinter as tk |
2 | 9 |
|
| 10 | + |
| 11 | +_OPERADORES_BINARIOS = { |
| 12 | + ast.Add: operator.add, |
| 13 | + ast.Sub: operator.sub, |
| 14 | + ast.Mult: operator.mul, |
| 15 | + ast.Div: operator.truediv, |
| 16 | +} |
| 17 | +_OPERADORES_UNARIOS = { |
| 18 | + ast.UAdd: operator.pos, |
| 19 | + ast.USub: operator.neg, |
| 20 | +} |
| 21 | + |
| 22 | + |
| 23 | +def avaliar_expressao(expressao: str) -> int | float: |
| 24 | + """Avalia somente números e os operadores exibidos pela calculadora.""" |
| 25 | + if not expressao or len(expressao) > 100: |
| 26 | + raise ValueError("Expressão vazia ou longa demais") |
| 27 | + |
| 28 | + try: |
| 29 | + arvore = ast.parse(expressao, mode="eval") |
| 30 | + except SyntaxError as erro: |
| 31 | + raise ValueError("Expressão inválida") from erro |
| 32 | + |
| 33 | + def avaliar(no: ast.AST, profundidade: int = 0) -> int | float: |
| 34 | + if profundidade > 20: |
| 35 | + raise ValueError("Expressão complexa demais") |
| 36 | + |
| 37 | + if isinstance(no, ast.Expression): |
| 38 | + return avaliar(no.body, profundidade + 1) |
| 39 | + |
| 40 | + if isinstance(no, ast.Constant): |
| 41 | + if isinstance(no.value, bool) or not isinstance(no.value, (int, float)): |
| 42 | + raise ValueError("Somente números são permitidos") |
| 43 | + return no.value |
| 44 | + |
| 45 | + if isinstance(no, ast.BinOp) and type(no.op) in _OPERADORES_BINARIOS: |
| 46 | + esquerda = avaliar(no.left, profundidade + 1) |
| 47 | + direita = avaliar(no.right, profundidade + 1) |
| 48 | + resultado = _OPERADORES_BINARIOS[type(no.op)](esquerda, direita) |
| 49 | + elif isinstance(no, ast.UnaryOp) and type(no.op) in _OPERADORES_UNARIOS: |
| 50 | + resultado = _OPERADORES_UNARIOS[type(no.op)]( |
| 51 | + avaliar(no.operand, profundidade + 1) |
| 52 | + ) |
| 53 | + else: |
| 54 | + raise ValueError("Operação não permitida") |
| 55 | + |
| 56 | + if isinstance(resultado, float) and not math.isfinite(resultado): |
| 57 | + raise ValueError("Resultado fora do intervalo permitido") |
| 58 | + return resultado |
| 59 | + |
| 60 | + return avaliar(arvore) |
| 61 | + |
| 62 | + |
3 | 63 | class Calculadora(tk.Tk): |
4 | | - def __init__(self): |
| 64 | + def __init__(self) -> None: |
5 | 65 | super().__init__() |
6 | 66 | self.title("Calculadora") |
7 | 67 | self.geometry("300x400") |
8 | | - |
9 | 68 | self.resultado_var = tk.StringVar() |
10 | | - self.limpar_var = tk.StringVar() |
| 69 | + self.criar_widgets() |
11 | 70 |
|
12 | | - self.create_widgets() |
| 71 | + def criar_widgets(self) -> None: |
| 72 | + entrada = tk.Entry( |
| 73 | + self, |
| 74 | + textvariable=self.resultado_var, |
| 75 | + font=("Arial", 24), |
| 76 | + bd=10, |
| 77 | + relief=tk.GROOVE, |
| 78 | + justify=tk.RIGHT, |
| 79 | + ) |
| 80 | + entrada.grid(row=0, column=0, columnspan=4) |
13 | 81 |
|
14 | | - def create_widgets(self): |
15 | | - # Entry para exibir os resultados |
16 | | - entry = tk.Entry(self, textvariable=self.resultado_var, font=('Arial', 24), bd=10, relief=tk.GROOVE, justify=tk.RIGHT) |
17 | | - entry.grid(row=0, column=0, columnspan=4) |
18 | | - |
19 | | - # Botões |
20 | 82 | botoes = [ |
21 | | - '7', '8', '9', '/', |
22 | | - '4', '5', '6', '*', |
23 | | - '1', '2', '3', '-', |
24 | | - '0', '.', '=', '+', |
25 | | - 'C' # Botão para limpar |
| 83 | + "7", "8", "9", "/", |
| 84 | + "4", "5", "6", "*", |
| 85 | + "1", "2", "3", "-", |
| 86 | + "0", ".", "=", "+", |
| 87 | + "C", |
26 | 88 | ] |
27 | 89 |
|
28 | | - row_val = 1 |
29 | | - col_val = 0 |
30 | | - |
31 | | - for botao in botoes: |
32 | | - tk.Button(self, text=botao, padx=20, pady=20, font=('Arial', 18), bd=8, relief=tk.RIDGE, command=lambda b=botao: self.botao_click(b)).grid(row=row_val, column=col_val) |
33 | | - col_val += 1 |
34 | | - if col_val > 3: |
35 | | - col_val = 0 |
36 | | - row_val += 1 |
| 90 | + linha = 1 |
| 91 | + coluna = 0 |
| 92 | + for rotulo in botoes: |
| 93 | + tk.Button( |
| 94 | + self, |
| 95 | + text=rotulo, |
| 96 | + padx=20, |
| 97 | + pady=20, |
| 98 | + font=("Arial", 18), |
| 99 | + bd=8, |
| 100 | + relief=tk.RIDGE, |
| 101 | + command=lambda valor=rotulo: self.botao_click(valor), |
| 102 | + ).grid(row=linha, column=coluna) |
| 103 | + coluna += 1 |
| 104 | + if coluna > 3: |
| 105 | + coluna = 0 |
| 106 | + linha += 1 |
37 | 107 |
|
38 | | - def botao_click(self, valor): |
39 | | - if valor == '=': |
| 108 | + def botao_click(self, valor: str) -> None: |
| 109 | + if valor == "=": |
40 | 110 | try: |
41 | | - resultado = eval(self.resultado_var.get()) |
42 | | - self.resultado_var.set(resultado) |
43 | | - except Exception as e: |
| 111 | + self.resultado_var.set(avaliar_expressao(self.resultado_var.get())) |
| 112 | + except (ArithmeticError, TypeError, ValueError): |
44 | 113 | self.resultado_var.set("Erro") |
45 | | - elif valor == 'C': |
| 114 | + elif valor == "C": |
46 | 115 | self.resultado_var.set("") |
47 | 116 | else: |
48 | | - current_text = self.resultado_var.get() |
49 | | - self.resultado_var.set(current_text + valor) |
| 117 | + self.resultado_var.set(self.resultado_var.get() + valor) |
50 | 118 |
|
51 | 119 |
|
52 | 120 | if __name__ == "__main__": |
|
0 commit comments