Desafio Python
Mini calculadora com tipos dinâmicos
Enunciado
Implemente uma função calcular(a, op, b) que receba dois valores e um operador ("+", "-", "*", "/"). Se a ou b for None, retorne None. Se algum dos dois não for int ou float (ou for bool), levante TypeError com a mensagem "operandos devem ser números". Se op não for uma das quatro operações, levante ValueError com a mensagem "operador inválido". Em divisão por zero, deixe o ZeroDivisionError propagar. Teste com uma lista de chamadas, capturando exceções e imprimindo o resultado ou a mensagem de erro.
Requisitos
- Retornar
NonequandoaoubforNone. - Rejeitar
boolcomo operando, mesmo sendo subclasse deint. - Levantar
TypeErroreValueErrorcom as mensagens exatas. - Capturar as exceções no código de teste e imprimir
Erro: <mensagem>.
Código inicial
def calcular(a, op, b):
# complete aqui
pass
casos = [
(2, "+", 3),
(None, "+", 1),
("2", "+", 3),
(10, "/", 0),
(2, "%", 3),
]
for a, op, b in casos:
try:
print(calcular(a, op, b))
except Exception as e:
print(f"Erro: {e}")
Saída esperada
5
None
Erro: operandos devem ser números
Erro: division by zero
Erro: operador inválido
Ver dica
Para excluir bool, cheque isinstance(x, bool) antes de aceitar int e float.
Mostrar solução
def calcular(a, op, b):
# None indica ausência de valor: devolvemos None sem erro.
if a is None or b is None:
return None
# bool é subclasse de int; rejeitamos explicitamente.
if isinstance(a, bool) or isinstance(b, bool):
raise TypeError("operandos devem ser números")
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("operandos devem ser números")
# Validamos o operador antes de calcular.
if op not in ("+", "-", "*", "/"):
raise ValueError("operador inválido")
if op == "+":
return a + b
if op == "-":
return a - b
if op == "*":
return a * b
return a / b # ZeroDivisionError propaga naturalmente
casos = [
(2, "+", 3),
(None, "+", 1),
("2", "+", 3),
(10, "/", 0),
(2, "%", 3),
]
for a, op, b in casos:
try:
print(calcular(a, op, b))
except Exception as e:
print(f"Erro: {e}")
Passo a passo
- A primeira checagem,
a is None or b is None, usaise devolveNoneimediatamente, sem levantar erro. - Em seguida rejeitamos
boolexplicitamente, poisTrueeFalsesão subclasses deinte não fazem sentido como operandos. - A checagem
isinstance(a, (int, float))garante que só números entram no cálculo; caso contrário,TypeErrorcom a mensagem pedida. - Validamos
opcontra a tupla de operadores válidos; se não estiver,ValueError. - Os
ifencadeados aplicam a operação correta; a divisão fica por último e deixaZeroDivisionErrorpropagar. - No bloco de teste, cada chamada é envolvida em
try/except Exception, imprimindo o resultado ouErro: <mensagem>.
Por que funciona
A função combina tipagem dinâmica com verificações explícitas: em vez de confiar no tipo inferido, inspecionamos os valores com isinstance e tratamos None e bool como casos especiais. Isso mostra como Python permite escrever código flexível, mas exige checagens quando queremos garantir contratos. As exceções TypeError e ValueError comunicam claramente o que deu errado, e o try/except no teste evita que o programa pare na primeira falha.
Erros comuns
- Usar
if not a or not bpara checarNone: isso também rejeita0e0.0. Prefiraa is None or b is None. - Esquecer de rejeitar
bool:calcular(True, "+", 1)retornaria2em vez de erro. - Checar o operador depois de calcular: se
opfor inválido, poderíamos lançar outro erro antes. Valide primeiro. - Capturar apenas
TypeErrorno teste:ZeroDivisionErroreValueErrorescapariam. Useexcept Exception.
Outra forma de resolver
Usando o módulo operator:
import operator
OPS = {"+": operator.add, "-": operator.sub, "*": operator.mul, "/": operator.truediv}
def calcular(a, op, b):
if a is None or b is None:
return None
if isinstance(a, bool) or isinstance(b, bool) or not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("operandos devem ser números")
if op not in OPS:
raise ValueError("operador inválido")
return OPS[op](a, b)
É preferível quando há muitas operações, pois evita a cadeia de if.
Saída esperada
5
None
Erro: operandos devem ser números
Erro: division by zero
Erro: operador inválido