Yaroslav Bibaev

Субъективный блог

24 May 2021

Именованные параметры функции

Один из способов сделать код более понятным для чтения — использовать именованные параметры для передачи в функцию. О том, что такое линейный код и как это влияет на чтение - я писал здесь.

Существуют два способа передачи параметров в функцию: виде позиционных параметров и в виде именованных. Позиционные параметры зависят, как не трудно догадаться, от позиции и не важно, какое они имеют наименование. Главное, чтобы нужный параметр соответствовал своему месту. Вот как это будет выглядеть на примере:

>>> def create_employee(first_name: str, last_name: str, birth_year: int) -> Employee:
...     ...
...
>>> employee = create_employee('Sarah', 'Parker', 1992)
<Employee>

Если поменять порядок аргументов, то код может упасть с ошибкой:

>>> employee = create_employee(1992, 'Sarah', 'Parker')
ValueError

Именованные параметры наоборот, не зависят от позиции и передается по имени аргумента:

>>> employee = create_employee(first_name='Sarah', last_name='Parker', birth_year=1992)
<Employee>
>>> employee = create_employee(birth_year=1992, first_name='Sarah', last_name='Parker')
<Employee>

Порядок аргументов не важен, главное чтобы совпадало имя, а вот код, в котором не совпадет имя аргумента, работать не будет и вызов функции завершится с ошибкой:

>>> employee = create_employee(first_name='Sarah', surname='Parker', birth_year=1992)
TypeError: create_employee() got an unexpected keyword argument 'surname'

Давайте усложним наш пример и добавим туда еще один аргумент years_of_experience:

>>> def create_employee(first_name: str, last_name: str, birth_year: int, years_of_experience:int) -> Employee:
...    ...
...
>>> employee = create_employee('Sarah', 'Parker', 1992, 7)
<Employee>

Если имена первых трех параметров можно было предположит по значениям, то наименование четвертого — загадка. Четвертый параметр — магическое число, чьё наименование получится узнать только “заглянув” в определение функции. Тем самым усложняя понимание кода для читающего. Теперь давайте перепишем этот вызов с помощью именованных параметров:

>>> employee = create_employee(first_name='Sarah', last_name='Parker', birth_year=1992, years_of_experience=7)
<Employee>

Теперь для понимания, что это за параметр и за что он отвечает, не требуется никаких дополнительных действий. Код с использованием именованных параметров стал понятным.

Использование именованных параметров в функциях с одним или двумя параметрами — не имеет особого смысла, так как все понятно из названия функции и контекста. Например, функция извлечения квадратного корня:

import math

assert math.sqrt(25) == 5.0

Или например, функция для сравнений двух значений:

import operator

assert operator.eq(1, 1) is True

При использовании внешних библиотек возникают сложности по причине, что мы не можем контролировать внешние интерфейсы. В таких случаях можно обложиться фасадом — функцию из внешней библиотеки оборачиваем в свой интерфейс. Это позволит получить контроль над интерфейсом и уменьшить зависимость кода от внешних библиотек. При смене зависимости остается исправить код только в фасаде, а не во всех местах, где мог использоваться вызов из внешней библиотеке.

Еще, как вариант, можно рассмотреть добавление комментариев к вызову функции:

employee = create_employee(
   'Sarah', # first_name
   'Parker', # last_name
   1992, # birth_year
   7, # years_of_experience
)

Получилось малость громоздко, но зато мы снизили ментальную нагрузку и улучшили опыт понимания кода. Конечно же, лучше такую проверку автоматизировать и добавить в линтер. Ведь люди склонны ошибаться, а машины — нет. Не нашел для питона что-то популярного, но есть вот такой плагин к flake8, который можно допилить до рабочего состояния и использовать во благо.