Главная / Курсы / Python / Функции: основы
# Глава 6. Функции: основы > Функции, которые производят значения, легче комбинировать новыми способами, чем те, которые производят сайд-эффекты. Марейн Хавербеке Синтаксис функций — показательная иллюстрация того, что питон заточен на разработку емкого и не отягощенного синтаксическими излишествами кода. Разберем, как функции объявляются и вызываются. ## Объявление и вызов Если вас интересует, как в питоне выглядит самая короткая функция, то вот она: ```python def f(): pass ``` Этот код практически бесполезен, зато демонстрирует минимальное объявление функции: ключевое слово `def`, имя функции и круглые скобки, в которых перечисляются параметры. Так как `f()` не принимает параметров, то скобки пусты. Затем идет блок с отступом — тело функции. И да, если функция состоит из единственного выражения, то разрешается оставить его на той же строке, что и имя: ```python def f(): pass ``` А вот и вызов функции: ```python f() ``` В отличие от C++, **в питоне нет перегрузки функций** — возможности создания нескольких функций с одним именем, но отличающихся типами параметров или их количеством. Дело в том, что выбор конкретной функции происходит на этапе компиляции. А питон к компилируемым языкам не относится, поэтому и не поддерживает перегрузку. ## Docstring Если функция не сводится к совсем уж тривиальным действиям, считается хорошим тоном оставлять в ней краткий комментарий с описанием. Он называется **docstring** и располагается на первой строчке тела функции: ```python def f(): """This function does completely nothing""" pass ``` IDE, умеющие работать с питоном, используют docstring для подсказок разработчику. В некоторых случаях имеет смысл писать развернутые многострочные комментарии: ```python def fetch_records(table, ids): """Selects records from the table which correspond to ids. Args: table: name of the table in the database. ids: A sequence of integers representing the id of each record to fetch. Returns: A dict mapping id to the corresponding record in table. """ pass ``` Заведите функцию `print_sum()`, которая принимает 2 числа и выводит в консоль их сумму. Добавьте в функцию docstring. {.task_text} Вызовите получившуюся функцию, передав в нее 3 и 8. {.task_text} ```python {.task_source #python_chapter_0060_task_0010} ``` После определения функции не забудьте ее вызвать с аргументами, указанными в задаче. {.task_hint} ```python {.task_answer} def print_sum(a, b): """Gets a & b arguments, prints sum.""" print(a+b) print_sum(3, 8) ``` ## Параметры и аргументы функции Напомним, что параметр — это имя, фигурирующее в определении функции. Он получает конкретное значение при вызове функции. А аргумент — это фактическое значение, переданное функции при вызове. Следующий пример демонстрирует объявление и использование функции `count_vowels()`. Она принимает два параметра: `s` — строку, в которой требуется посчитать количество гласных букв, и `n` — длину строки. Причем параметр `n` является опциональным и по умолчанию выставляется в `None` — объект, означающий отсутствие значения. Для определения, относится ли буква к гласным, заведена вспомогательная функция `is_vowel()`. ```python {.example_for_playground} def is_vowel(c): return c.lower() in ['a', 'e', 'i', 'o', 'u', 'y'] def count_vowels(s, n = None): if n is None: n = len(s) if n == 1: return is_vowel(s[n - 1]) return count_vowels(s, n - 1) + is_vowel(s[n - 1]) print(count_vowels("Pascal!")) ``` ``` 2 ``` Разумеется, подсчет гласных букв можно (и нужно) реализовать на питоне гораздо, гораздо проще — он уместится в одну строку. Зато этот переусложненный пример показывает рекурсивный вызов функции, использование вспомогательной функции и обращение к символу строки по индексу (через квадратные скобки `[]`). ## Позиционные и именованные аргументы {#block-pos-named-args} В питоне есть две категории аргументов: позиционные и именованные. **Позиционные аргументы** передаются в функцию в строгой очередности (которая указана в объявлении функции). Позиционные аргументы идут без указания имен: ```python res = count_vowels("Some string", None) ``` ```python text = "It's a recursion example!" text_len = len(text) res = count_vowels(text, text_len) ``` Если у параметра указано значение по умолчанию, то его разрешается опустить при вызове: ```python res = count_vowels("python") ``` **Именованные аргументы** передаются в функцию с указанием конкретного имени, к которому привязывается значение: ```python res = count_vowels(s = "Some string", n = None) ``` ```python res = count_vowels(s = "python") ``` Важно не путать передачу именованных аргументов и присвоение переменной при вызове функции через [оператор моржа](/courses/python/chapters/python_chapter_0030#block-walrus) `:=`: ```python res = count_vowels(q := "python") print(q) ``` ``` python ``` В данном случае `q` — созданная в момент вызова функции переменная, а не именованный аргумент. Ей присваивается строка `"python"`. И эта же строка попадает в качестве аргумента в функцию `count_vowels()`. Переменная `q` при этом продолжает существовать и после вызова функции. При вызове в функцию можно передавать и позиционные, и именованные аргументы. Главное — не смешивать их в кучу: позиционные аргументы идут в начале, и только после них следуют именованные. Представим функцию, принимающую 4 параметра: ```python def f(a, b, c, d): pass ``` Вызов этой функции с передачей вначале двух позиционных, а затем двух именованных аргументов, отработает корректно: ```python f(1, 2, c=3, d=4) ``` А вот чередование позиционных и именованных аргументов приведет к ошибке: ```python f(a=1, 2, c=3, d=4) ``` На данной строчке сгенерируется исключение `SyntaxError: positional argument follows keyword argument`. Именованные аргументы также удобны тем, что их можно передавать в функцию **в любом порядке:** ```python f(a=1, b=2, c=3, d=4) f(d=4, c=3, a=1, b=2) ``` Исправьте все некорректные вызовы функции `print_last_item()`. Внутри этой функции вызывается метод строки `split()`, по разделителю `delimiter` превращающий строку в список подстрок. Из получившегося списка на экран выводится последний элемент (обращение по индексу -1 к списку означает взятие последнего элемента). {.task_text} ```python {.task_source #python_chapter_0060_task_0020} def print_last_item(s, delimiter): print(s.split(delimiter)[-1]) # Нужно вывести расширение файла print_last_item(s="/var/log/msg.log") # Нужно вывести последнее слово в предложении print_last_item("Complex is better than complicated") # Нужно вывести имя последней директории в пути (August) print_last_item("/home/george/Documents/2023/August") ``` Обратите внимание на комментарии в коде. {.task_hint} ```python {.task_answer} def print_last_item(s, delimiter): print(s.split(delimiter)[-1]) # Нужно вывести расширение файла print_last_item(s="/var/log/msg.log", delimiter=".") # Нужно вывести последнее слово в предложении print_last_item("Complex is better than complicated", " ") # Нужно вывести имя последней директории в пути (August) print_last_item("/home/george/Documents/2023/August", "/") ``` Как выбрать, когда применять позиционные аргументы, а когда — именованные? В первую очередь ориентируйтесь на читабельность кода. И лишь затем — на лаконичность. Если указание имени для аргумента не привносит в код ясности и важной информации, то лаконичнее использовать позиционные аргументы. В питоне есть возможность в сигнатуре функции принудительно зафиксировать вид параметра: чтобы он был исключительно позиционным или только именованным. Мы [обсудим](/courses/python/chapters/python_chapter_0260#block-keyword-only) это в главе про вариативные функции. ## Изменяемые и не изменяемые аргументы Может ли аргумент быть изменен внутри функции так, что эти изменения отразятся за ее пределами? Да, и это зависит от типа передаваемого объекта (а не от сигнатуры функции, как в некоторых языках). Любая переменная в питоне в действительности является ссылкой на некий объект в памяти. Объекты в свою очередь делятся на изменяемые и не изменяемые. Более подробно мы раскроем эту тему [в главе про типы данных.](/courses/python/chapters/python_chapter_0080#block-mutable-immutable-types) А пока скажем, что к неизменяемым объектам относятся например числа, строки, кортежи. Они называются неизменяемыми, потому что при каждой модификации объекта у него меняется id. Id — обязательный атрибут всех объектов, он есть даже у чисел. Изменение id фактически означает, что объект перестает существовать, вместо него появляется новый, на который начинает ссылаться переменная. А изменяемые объекты — это списки, словари и так далее. При модификации объектов этих типов их id не меняется, переменная продолжает ссылаться на тот же объект в памяти. Поэтому все объекты передаются в функцию по ссылке, но нужно проявлять осторожность при работе с объектами изменяемых типов. Их модификация внутри функции отразится и за ее пределами, поскольку будет затронута та же область памяти (объект с тем же id). С неизменяемыми типами такой проблемы нет: при попытке модифицировать объект неизменяемого типа внутри функции выделится новая область памяти с новым id. Этот нюанс при работе с аргументами функции — популярнейшая причина ошибок начинающих питонистов. Сколько часов из-за него было потрачено на дебаг, сколько клавиатур разбито... Не наступайте на эти грабли. Помните: если внутри функции вы модифицируете аргумент-число, эти изменения не покинут пределов функции. Они вносятся в копию переменной. И наоборот, если передаете в функцию список, четко понимайте, что все изменения в этом списке сохранятся после выхода из функции. ## Оператор return Функция может принимать произвольное количество аргументов или не принимать их вовсе. А что насчет возвращаемого значения? Аналогично: функция может возвращать произвольное количество значений или не возвращать ничего. Возвращаемые значения указываются после оператора `return` и перечисляются через запятую. Возвращение двух значений на примере расчета площади круга и длины окружности по радиусу `r`: ```python import math def get_circle_info(r): return math.pi * r * r, math.pi * 2.0 * r area, circumference = get_circle_info(r=5.2) ``` Вызов `return` для нескольких значений на самом деле неявно оборачивает их в кортеж — неизменяемый вариант массива, который мы [рассмотрим позже.](/courses/python/chapters/python_chapter_0120/) А присвоение в переменные нескольких значений, полученных из функции — всего лишь синтаксический сахар над распаковкой кортежа. Для создания кортежа используются круглые скобки: `()`. Эти два варианта вызова `return` являются эквивалентными: ```python return s, r ``` ```python return (s, r) ``` Напишите функцию `triangle_area(b, h)`, принимающую длину основания треугольника и его высоту. По ним функция должна вернуть площадь треугольника. Формула простая: половина произведения основания на высоту. Например, `triangle_area(b=4, h=8)` равен 16. {.task_text} ```python {.task_source #python_chapter_0060_task_0030} ``` Формула площади треугольника с основанием `b` и высотой `h`: `0.5 * b * h`. {.task_hint} ```python {.task_answer} def triangle_area(b, h): return 0.5 * b * h ``` Если функция ничего не возвращает, писать в конце `return` не обязательно: ```python import pickle def serialize_data_to_file(data, filepath): pickle.dump(data, open(filepath, "wb")) ``` В этом примере мы подключаем модуль `pickle` для сериализации и десериализации объектов питона в поток байтов. Функция `serialize_data_to_file` - всего лишь обертка для превращения произвольных данных в поток байтов и сохранения его в интересующий файл. Так как функция ничего не возвращает, `return` в ней отсутствует. Функция, внутри которой есть `return`, но без возвращаемого значения, либо в которой `return` вовсе отсутствует, на самом деле неявно возвращает `None`. `None` — специальный объект, означающий отсутствие значения: ```python res = serialize_data_to_file(user_stats, "/tmp/stats_latest") print(res) ``` ``` None ``` ## Резюмируем - Для объявления функции используется ключевое слово `def`, после которого идут имя функции, параметры и тело функции. - Аргументы функции бывают позиционными и именованными. - Аргументы любых типов передаются в функцию всегда только по ссылке. - Функция может возвращать произвольное количество значений. В таком случае они неявно оборачиваются в кортеж. - Для документирования поведения функции используется docstring — комментарий в начале тела функции.
Отправка...
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!