В этом посте мы разберём простую историю о том, как опциональные аргументы в Python помогают разработчикам писать код лучше.
Представь, что ты разрабатываешь софт для крупной судоходной компании. И тебе поручили реализовать функцию, которая считает сбор за проход судов в зависимости от веса груза. Просто и легко:
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
Достаточно просто.
Но однажды твоя программа будет работать в другой стране, скажем, в США. Есть одна проблема — тебе нужно использовать фунты вместо килограммов для расчёта сбора. Не проблема, вот:
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
Дальше — сложнее, но вот ещё одно требование — на основе булевого флага тебе нужно выбрасывать исключение, если вес превышает 1000 килограммов для определённых направлений:
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
Видишь проблему? В этом игрушечном примере у тебя получилась функция с тремя позиционными аргументами, последние два из которых одного типа. Конечный пользователь, или ты сам как разработчик, легко можешь забыть, какой из них должен идти первым, и перепутать их местами. Благодаря одинаковому типу программа на Python не упадёт, и ты получишь логическую ошибку:
calculate_fees(2000, True, False)
или
calculate_fees(2000, False, True)
Можно использовать именованные (keyword) аргументы со значениями по умолчанию, и это хорошая практика:
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
Но проблема не решена. Чтобы решить её, нужно заставить конечного пользователя использовать именованные аргументы явно. Для этого нужно добавить одну звёздочку в начале списка ключевых аргументов:
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
Вот и всё, в следующий раз при вызове этой функции ты получишь ошибку:
>>> calculate_fees(2000, True, False)
TypeError: calculate_fees() takes 1 positional argument but 3 were given
Подробнее: PEP-3102