En este post, repasaremos una historia sencilla de cómo los argumentos opcionales en Python ayudan a los desarrolladores a escribir mejor código.
Imagina que estás desarrollando software para una gran empresa naviera. Y te encargan implementar una función para calcular las tarifas de paso de los barcos según el peso de la carga. Fácil y sencillo:
WEIGHT_RATES = [
( 10, 10.55),
( 5, 5.05),
( 2, 3.35),
( 0, 1.25)
]
def calculate_fees(weight):
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate
Bastante simple.
Pero algún día tu programa funcionará en otro país, digamos, Estados Unidos. Hay un problema — tienes que usar libras en vez de kilogramos para calcular la tarifa. No hay problema, ahí lo tienes:
def calculate_fees(weight, pnds):
if pnds:
weight /= 2.2
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate
Esto se vuelve cada vez más difícil, pero aquí viene otro requisito — según un flag booleano, tienes que lanzar una excepción si el peso supera los 1000 kilogramos para ciertas rutas:
def calculate_fees(weight, pnds, exceed):
if pnds:
weight /= 2.2
if exceed and weight > 1000:
raise Exception("Weight can't exceed 1000 kg")
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate
¿Ves el problema? En este ejemplo simplificado, tienes una función con 3 argumentos posicionales, y los dos últimos son del mismo tipo. El usuario final, o tú mismo como desarrollador, puede fácilmente olvidar cuál va primero y confundirlos. Gracias a que son del mismo tipo, el programa en Python no fallará, y obtendrás un error lógico:
calculate_fees(2000, True, False)
o
calculate_fees(2000, False, True)
Puedes usar argumentos con palabra clave y valores por defecto, y es una buena práctica:
def calculate_fees(weight, pnds=False, exceed=False):
if pnds:
weight /= 2.2
if exceed and weight > 1000:
raise Exception("Weight can't exceed 1000 kg")
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate
Pero el problema no está resuelto. Para resolverlo, tenemos que obligar al usuario final a usar argumentos con palabra clave de forma explícita. Para hacerlo, hay que añadir un asterisco al principio de la lista de argumentos con palabra clave:
def calculate_fees(weight, *, pnds=False, exceed=False):
if pnds:
weight /= 2.2
if exceed and weight > 1000:
raise Exception("Weight can't exceed 1000 kg")
if weight < 0:
raise ValueError("Can't calculate shipping charge of negative weights")
for min_weight, rate in WEIGHT_RATES:
if weight > min_weight:
return weight * rate
Eso es todo, la próxima vez que llames a esta función obtendrás un error:
>>> calculate_fees(2000, True, False)
TypeError: calculate_fees() takes 1 positional argument but 3 were given
Más información: PEP-3102