Validación de Argumentos de Funciones
Al crear funciones en Python, es importante verificar que los argumentos pasados a sus funciones sean válidos antes de continuar con la lógica principal de la función. En este paso, aprenderemos varias técnicas para validar los argumentos de las funciones.
Creando un Nuevo Archivo Python
Creemos un nuevo archivo para trabajar con conceptos de validación:
- Haga clic en el menú "Archivo" en el WebIDE
- Seleccione "Nuevo Archivo"
- Ingrese
validate_args.py como nombre de archivo
- Haga clic en "Aceptar"
Validación Básica con Condicionales
La forma más sencilla de validar argumentos es usar declaraciones condicionales. Comencemos con algunas validaciones 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}")
Guarde y ejecute:
python3 validate_args.py
Salida:
Area of rectangle: 15
Error: Length must be a number
Error: Width must be positive
Esta función valida tanto los tipos como los valores de sus argumentos antes de realizar cualquier cálculo. Cuando se detectan argumentos no válidos, se generan mensajes de error apropiados.
Usando Aserciones para la Validación
Otra forma de validar argumentos es usar aserciones (assertions). Las aserciones son declaraciones que generan un AssertionError si no se cumple una condición:
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}")
Guarde y ejecute:
python3 validate_args.py
Salida:
Discounted price: $80.0
Error: Discount must be between 0 and 100
Las aserciones son útiles para el desarrollo y la depuración, pero pueden deshabilitarse en el código de producción, por lo que no siempre son la mejor opción para la validación en aplicaciones del mundo real.
Usando Sugerencias de Tipo (Type Hints) para la Documentación
Python 3.5+ admite sugerencias de tipo (type hints), que pueden ayudar a documentar los tipos esperados de los argumentos de la función y los valores de retorno. Si bien las sugerencias de tipo no realizan la validación en tiempo de ejecución por sí solas, proporcionan documentación útil y pueden ser verificadas por herramientas 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}")
Guarde y ejecute:
python3 validate_args.py
Salida:
Average: 3.0
Error: Cannot calculate average of empty list
Error: All elements must be numbers
Tenga en cuenta que las sugerencias de tipo (list[float] y -> float) no realizan ninguna validación por sí mismas; aún necesitamos escribir nuestro propio código de validación. Sirven como documentación y pueden ser verificadas por herramientas externas.
Creando una Función Robusta con Validación
Ahora, apliquemos todas estas técnicas para construir una función robusta que calcule el costo total de los artículos con un descuento:
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}")
Guarde y ejecute:
python3 validate_args.py
Salida:
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 función demuestra una validación robusta al:
- Verificar los tipos de todas las entradas
- Validar el rango de valores numéricos
- Proporcionar mensajes de error detallados
- Establecer valores predeterminados razonables para los parámetros opcionales
- Usar cadenas de documentación (docstrings) para documentar las entradas y los valores de retorno esperados
Al implementar una validación exhaustiva en sus funciones, puede prevenir errores, proporcionar una mejor retroalimentación a los usuarios y hacer que su código sea más robusto y mantenible.