Главная
Серии Обо мне Подписка
Опциональные аргументы ДОЛЖНЫ быть ключевыми (Python 3)

Опциональные аргументы ДОЛЖНЫ быть ключевыми (Python 3)

В этом посте мы разберём простую историю о том, как опциональные аргументы в 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

Liked this? I publish one deep-dive every week.

Join 4,000+ engineers. No BS.

Get the newsletter

Enjoyed what you just read? Others like these as well:

Python Interview Questions for Mid-Level Developers

Python Interview Questions for Beginners (Junior)

Design Patterns Suck