# Глава 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. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!