3 приема для определения функций в Python

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

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

В статье мы рассмотрим 3 полезных приема, которые пригодятся Python-программистам при написании функций.

Обратите внимание, что данный материал не содержит общепринятые лучшие практики определения функций, такие как продуманные имена, конкретно предусмотренные цели (а не смешанные задачи) и грамотные строки документации docstrings. Статья рассматривает вопрос с более технической точки зрения. 

Прервем потоки слов и приступим сразу к делу! 

Аргументы по умолчанию 

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

Рассмотрим способ, который упрощает вызовы функции, принимающей несколько аргументов. Речь пойдет об установке аргументов по умолчанию. Эта функциональность широко применяется в стандартной библиотеке Python. Ниже представлен пример сигнатуры вызова встроенной функции sorted:

sorted(iterable, /, *, key=None, reverse=False)

В функции параметры key и reverse имеют соответствующие значения по умолчанию: аргумент key определяет способ сортировки (None означает предустановленный лексикографический или числовой порядок), а аргумент reverse указывает на убывающий порядок. В большинстве случаев при вызове данной функции не нужно устанавливать key и reverse. Но когда требуется определить пользовательскую операцию сортировки, то аргумент key устанавливается, как показано ниже: 

>>> objects = [-1, -5, 3]
>>> sorted(objects, key=abs)
[-1, 3, -5]

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

Подсказки типов 

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

def greeting(person, message):
pass

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

greeting("John Smith", "Hi")

Также возможно, что есть класс Person, и вы должны вызвать данную функцию с его экземпляром, как в примере:  

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name


person = Person("John Smith")
greeting(person, "Hi")

Как видно, иногда функция может озадачить пользователя. Такая неоднозначность снимается путем применения для нее подсказок типов. Если функция принимает строку в качестве аргумента person, то пишем так: 

def greeting(person: str, message: str):
pass

Если она принимает не строку, а экземпляр Person, то делаем так: 

class Person:
    pass

def greeting(person: Person, message: str):
    pass

Устанавливая подсказки типов в определении функции, пользователи получают выгодное преимущество в виде содержательных подсказок при вызове функции в IDE, например PyCharm. Соответствующий скриншот прилагается: 

Содержательные подсказки типов в PyCharm

PyCharm или любой плагин для анализа кода в VSC использует подсказки типов для проверки кода на предмет предоставления подсказок.

Отметим еще один момент. Если функция возвращает значение, вы можете предоставить подсказки типов для этого возвращаемого значения, как показано ниже: 

def greeting(person: Person, message: str) -> str:
return f"{message}, {person.first_name}!"

Формат подсказки типов возвращаемого значения  —  -> type, который указывается после круглых скобок в заголовке функции. 

Конструкции *args и **kwargs

Как правило, функции включают определенное количество аргументов. Однако иногда точно неизвестно, какие именно аргументы может передавать пользователь. Например, встроенная функция print принимает любое количество объектов: 

message0 = "Hello"
message1 = "World"
ending_symbol = "!"

print(message0, message1, ending_symbol)

# вывод: Hello World !

Почему удается это сделать с помощью функции print? Посмотрим на сигнатуру вызова: 

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

Причина, по которой мы можем вывести любое количество объектов, кроется в параметре *objects. Обратите внимание на префикс * в его имени. Он означает, что в функцию можно передать переменное количество аргументов. Как видно, эта функциональность обеспечивает гибкость функции print. Теперь рассмотрим ее действие в функции stringify

def stringify(*objects) -> list[str]:
print(type(objects), objects)
return [str(object) for object in objects]

В этой функции мы разрешаем пользователю передать переменное количество аргументов и преобразуем каждый аргумент в объект str. Отметим, что при вызове данной функции с несколькими позиционными аргументами, эти аргументы образуют объект tuple

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

>>> stringify(1, (2, 4), False, {2, 3, 5, 7})
<class 'tuple'> (1, (2, 4), False, {2, 3, 5, 7})
['1', '(2, 4)', 'False', '{2, 3, 5, 7}']

Аналогичным образом можно определить переменное количество именованных аргументов. Синтаксис этой конструкции выглядит так  —  **kwargs, с двумя символами * перед именем параметра. Наличие **kwargs в функции означает, что она может принимать любое количество именованных аргументов. 

Для примера создадим табель успеваемости студента по учебным курсам и применим следующую функцию:  

def create_report(name, **grades):
print(f"Grade Report for {name}")
for course, grade in grades.items():
print(f"{course}: {grade}")

Поскольку студент может посещать разные курсы, то **grades обеспечит необходимую гибкость решения. Как видно, параметр grades представляет собой объект dict. С помощью элементов мы можем получить доступ к его парам ключ-значение и задействовать их в цикле for. Рассмотрим несколько вызовов: 

>>> create_report("John", math=95, chemistry=100, physics=98)
# вывод следующих строк:
Grade Report for John
math: 95
chemistry: 100
physics: 98

>>> create_report("Zoe", biology=93, geography=97)
# вывод следующих строк:
Grade Report for Zoe
biology: 93
geography: 97

При вызове данной функции мы перечисляем аргументы как именованные, и Python объединяет их в объект dict для последующего использования в теле функции. 

Несмотря на предоставляемую гибкость в определении функции, следует обоснованно оперировать *args и **kwargs, как в выше описанных сценариях. Не злоупотребляйте данной функциональностью, поскольку именованные, определенные аргументы являются наиболее простыми. 

Заключение

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

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Yong Cui: 3 Things to Consider When You Define Functions

Предыдущая статьяСначала графдизайн создать, потом код написать
Следующая статьяReact в плагине Sketch: проблема загрузки исходных данных