Validando Argumentos de Função
Ao criar funções em Python, é importante verificar se os argumentos passados para suas funções são válidos antes de prosseguir com a lógica principal da função. Nesta etapa, aprenderemos várias técnicas para validar argumentos de função.
Criando um Novo Arquivo Python
Vamos criar um novo arquivo para trabalhar com conceitos de validação:
- Clique no menu "File" no WebIDE
- Selecione "New File"
- Insira
validate_args.py como o nome do arquivo
- Clique em "OK"
Validação Básica com Condicionais
A maneira mais simples de validar argumentos é usar instruções condicionais. Vamos começar com algumas validações básicas:
def calculate_rectangle_area(length, width):
"""Calculate the area of a rectangle, validating inputs."""
## Validate that inputs are numbers
if not isinstance(length, (int, float)):
raise TypeError("Length must be a number")
if not isinstance(width, (int, float)):
raise TypeError("Width must be a number")
## Validate that inputs are positive
if length <= 0:
raise ValueError("Length must be positive")
if width <= 0:
raise ValueError("Width must be positive")
## Calculate the area
return length * width
## Test with valid inputs
try:
area = calculate_rectangle_area(5, 3)
print(f"Area of rectangle: {area}")
except (TypeError, ValueError) as e:
print(f"Error: {e}")
## Test with invalid types
try:
area = calculate_rectangle_area("5", 3)
print(f"Area of rectangle: {area}")
except (TypeError, ValueError) as e:
print(f"Error: {e}")
## Test with invalid values
try:
area = calculate_rectangle_area(5, -3)
print(f"Area of rectangle: {area}")
except (TypeError, ValueError) as e:
print(f"Error: {e}")
Salve e execute:
python3 validate_args.py
Saída:
Area of rectangle: 15
Error: Length must be a number
Error: Width must be positive
Esta função valida os tipos e os valores de seus argumentos antes de realizar quaisquer cálculos. Quando argumentos inválidos são detectados, mensagens de erro apropriadas são levantadas.
Usando Asserções para Validação
Outra maneira de validar argumentos é usar asserções. Asserções são declarações que levantam um AssertionError se uma condição não for atendida:
def calculate_discount(price, discount_percent):
"""Calculate the discounted price."""
## Assert that inputs are valid
assert isinstance(price, (int, float)), "Price must be a number"
assert isinstance(discount_percent, (int, float)), "Discount must be a number"
assert price >= 0, "Price cannot be negative"
assert 0 <= discount_percent <= 100, "Discount must be between 0 and 100"
## Calculate the discount
discount_amount = price * (discount_percent / 100)
return price - discount_amount
## Test with valid inputs
try:
discounted_price = calculate_discount(100, 20)
print(f"Discounted price: ${discounted_price}")
except AssertionError as e:
print(f"Error: {e}")
## Test with invalid discount percentage
try:
discounted_price = calculate_discount(100, 120)
print(f"Discounted price: ${discounted_price}")
except AssertionError as e:
print(f"Error: {e}")
Salve e execute:
python3 validate_args.py
Saída:
Discounted price: $80.0
Error: Discount must be between 0 and 100
As asserções são úteis para desenvolvimento e depuração, mas podem ser desativadas no código de produção, portanto, nem sempre são a melhor escolha para validação em aplicações do mundo real.
Usando Dicas de Tipo para Documentação
Python 3.5+ suporta dicas de tipo (type hints), que podem ajudar a documentar os tipos esperados de argumentos de função e valores de retorno. Embora as dicas de tipo não realizem validação em tempo de execução por si só, elas fornecem documentação útil e podem ser verificadas por ferramentas externas como mypy:
def calculate_average(numbers: list[float]) -> float:
"""Calculate the average of a list of numbers."""
if not numbers:
raise ValueError("Cannot calculate average of empty list")
if not all(isinstance(n, (int, float)) for n in numbers):
raise TypeError("All elements must be numbers")
return sum(numbers) / len(numbers)
## Test with valid input
try:
avg = calculate_average([1, 2, 3, 4, 5])
print(f"Average: {avg}")
except (TypeError, ValueError) as e:
print(f"Error: {e}")
## Test with empty list
try:
avg = calculate_average([])
print(f"Average: {avg}")
except (TypeError, ValueError) as e:
print(f"Error: {e}")
## Test with non-numeric elements
try:
avg = calculate_average([1, 2, "3", 4, 5])
print(f"Average: {avg}")
except (TypeError, ValueError) as e:
print(f"Error: {e}")
Salve e execute:
python3 validate_args.py
Saída:
Average: 3.0
Error: Cannot calculate average of empty list
Error: All elements must be numbers
Observe que as dicas de tipo (list[float] e -> float) não realizam nenhuma validação por si só - ainda precisamos escrever nosso próprio código de validação. Elas servem como documentação e podem ser verificadas por ferramentas externas.
Criando uma Função Robusta com Validação
Agora, vamos aplicar todas essas técnicas para construir uma função robusta que calcula o custo total de itens com um desconto:
def calculate_total_cost(items=None, tax_rate=0, discount=0):
"""
Calculate the total cost of items with tax and discount.
Args:
items: List of (item_name, price) tuples
tax_rate: Tax rate percentage (0-100)
discount: Discount percentage (0-100)
Returns:
A dictionary with the total, subtotal, tax amount, and discount amount
"""
## Validate items
if items is None:
items = []
if not isinstance(items, list):
raise TypeError("Items must be a list")
## Validate each item in the list
for i, item in enumerate(items):
if not isinstance(item, tuple) or len(item) != 2:
raise ValueError(f"Item {i} must be a tuple of (name, price)")
name, price = item
if not isinstance(name, str):
raise TypeError(f"Name of item {i} must be a string")
if not isinstance(price, (int, float)):
raise TypeError(f"Price of item {i} must be a number")
if price < 0:
raise ValueError(f"Price of item {i} cannot be negative")
## Validate tax_rate and discount
if not isinstance(tax_rate, (int, float)):
raise TypeError("Tax rate must be a number")
if not isinstance(discount, (int, float)):
raise TypeError("Discount must be a number")
if not (0 <= tax_rate <= 100):
raise ValueError("Tax rate must be between 0 and 100")
if not (0 <= discount <= 100):
raise ValueError("Discount must be between 0 and 100")
## Calculate the total
subtotal = sum(price for _, price in items)
discount_amount = subtotal * (discount / 100)
tax_amount = (subtotal - discount_amount) * (tax_rate / 100)
total = subtotal - discount_amount + tax_amount
return {
"subtotal": subtotal,
"discount_amount": discount_amount,
"tax_amount": tax_amount,
"total": total
}
## Test with valid inputs
shopping_cart = [
("Laptop", 1000),
("Mouse", 25),
("Keyboard", 45)
]
try:
result = calculate_total_cost(shopping_cart, tax_rate=8.5, discount=10)
print("Shopping Cart Total:")
for key, value in result.items():
print(f" {key.replace('_', ' ').title()}: ${value:.2f}")
except (TypeError, ValueError) as e:
print(f"Error: {e}")
## Test with invalid item
try:
invalid_cart = [
("Laptop", 1000),
("Mouse", "twenty-five"), ## Invalid price
("Keyboard", 45)
]
result = calculate_total_cost(invalid_cart)
print(result)
except (TypeError, ValueError) as e:
print(f"Error with invalid item: {e}")
Salve e execute:
python3 validate_args.py
Saída:
Shopping Cart Total:
Subtotal: $1070.00
Discount Amount: $107.00
Tax Amount: $81.86
Total: $1044.86
Error with invalid item: Price of item 1 must be a number
Esta função demonstra uma validação robusta por:
- Verificando os tipos de todas as entradas
- Validando a faixa de valores numéricos
- Fornecendo mensagens de erro detalhadas
- Definindo padrões razoáveis para parâmetros opcionais
- Usando docstrings para documentar as entradas e valores de retorno esperados
Ao implementar uma validação completa em suas funções, você pode evitar erros, fornecer um feedback melhor aos usuários e tornar seu código mais robusto e sustentável.