Сегодня рассказываем об одной из основных вещей в программировании — функциях. Они есть почти во всех современных языках программирования, а мы расскажем про них на примере Python.
Это статья про теоретические вещи, хотя и с примерами. Если хотите собрать что-нибудь интересное, вот пара наших проектов:
Что такое функция
Функции в Python (да и в программировании в целом) нужны для того, чтобы делать код проще и удобнее.
Самый простой способ написать программу — точно прописать инструкции для компьютера, которые он будет выполнять одну за другой. При таком способе разработки много кода повторяется, и легко запутаться, какая часть программы за что отвечает.
Функции — это готовые элементы программы, которым можно присвоить имена и научить выполнять нужные алгоритмы. Можно сказать функции: я буду приносить тебе вот эти данные, а тебе с ними нужно будет сделать то-то и то-то.
При таком подходе повторять код не нужно, и программа становится более читаемой.
Функциональная парадигма программирования
Одна из основных характеристик программ — парадигма программирования. Это то, как разработчик пишет код, какие подходы и технологии языка использует, а какие — нет.
Многие думают, что функциональное программирование — это когда используешь функции, но это не так.
Смысл функционального программирования в том, что мы не задаём последовательность нужных нам команд, а описываем взаимодействие между ними и подпрограммами. Это похоже на то, как работают объекты в объектно-ориентированном программировании, только здесь это реализуется на уровне всей программы.
Если хотите разобраться получше, что такое функциональное программирование, почитайте нашу статью об этом, там подробно и с примерами.
Синтаксис функций
Для создания функций в Python есть ключевой параметр-слово def
. В Python def
означает, что сейчас будет объявлена функция.
Порядок определения функции такой:
- пишем
def
, чтобы Python понял, что дальше идёт функция; - даём функции имя;
- после имени в скобках перечисляем аргументы, с которыми будет работать функция;
- описываем алгоритм работы с аргументами.
Вот пример функции describe_pet
, которая выводит описание домашнего питомца. Эта функция принимает два аргумента: тип животного и его имя. Если вызвать её и передать эти два аргумента, она вставит их в строку и выведет на экран:
# объявляем функцию и даём ей имя
def describe_pet(animal_type, pet_name):
"""
Выводит информацию о питомце.
:param animal_type: Тип животного (например, "кошка", "собака").
:param pet_name: Имя питомца.
"""
print(f"У вас есть {animal_type}, ее зовут {pet_name}.")
# вызываем функцию и передаём ей аргументы для работы
describe_pet("кошка", "Мурка")
Результат вызова:
У вас есть кошка, ее зовут Мурка.
Возвращаемые значения (return)
Функции можно запрограммировать так, чтобы они возвращали результат своей работы. Для этого в конце нужно поставить слово return
и после него указать, что именно мы хотим получить из функции.
Пример небольшой функции, в которую можно передавать два числа и получить результат их сложения:
# объявляем функцию
def add(a, b):
# говорим функции возвращать результат сложения
return a + b
# сохраняем результат в переменную
result = add(3, 5)
# выводим результат на экран
print(result)
Что можно возвращать? В примере выше мы возвращаем число, но функция может возвращать любой указанный после ключевого слова объект.
Return
останавливает работу функции, поэтому она завершит выполнение на этой строке. Если в каком-то месте мы хотим прекратить работу функции, можно просто написать слово return
без указания того, что нужно вернуть. Это может быть полезно, если внутри функции проверяются какие-то условия и в результате одного из них она должна остановиться.
Распаковка возвращаемых значений — разделение нескольких значений из функции. Эта операция возможна, если функция возвращает не одно, а несколько значений.
Для этого нужно перечислить то, что мы хотим вернуть. Все перечисленные объекты вернутся в виде одного кортежа — набора значений, которые нельзя изменить. После этого можно через запятую указать переменные и вызвать функцию — Python достанет значения из кортежа и разложит по переменным в том порядке, который мы указали.
Пример — возвращаем минимальное и максимальное значение из списка:
# объявляем функцию
def min_max(numbers):
# просим вернуть минимальное и максимальное значение
# из коллекции элементов, которую мы передадим
# эти значения вернутся в виде кортежа
return min(numbers), max(numbers)
# распаковываем значения в отдельные переменные minimum и maximum
minimum, maximum = min_max([10, 20, 30])
# выводим значения на экран
print(f"Минимум: {minimum}, Максимум: {maximum}")
При вызове этого блока кода на экране получим:
Минимум: 10, Максимум: 30
Пустая функция — функция, которая ничего не делает и ничего не возвращает. Такую функцию можно использовать для задела кода на будущее, если потом прописать для неё алгоритм действий.
Для создания пустой функции её нужно объявить и в теле функции прописать только одно слово — pass
.
# объявляем пустую функцию
def placeholder_function():
pass
Функции и процедуры
Процедуры в Python — это функции, которые ничего не возвращают.
Наша функция describe_pet()
про вывод информации о домашнем животном — это процедура, потому что в ответ мы ничего не получаем. А add()
с возвращением суммы двух чисел — классическая функция.
👉 Правило простое: чтобы превратить функцию в процедуру в Python, нужно не использовать команду return() в конце функции.
Объявление и вызов функций
Функцию можно объявить в любом месте кода, но вызвать — только после объявления.
Так происходит потому, что Python — интерпретируемый язык, и весь код перед исполнением будет переработан в список инструкций для машины. Этот список интерпретатор Python будет выполнять построчно. Когда интерпретатор встречает вызов функции, к этому моменту он уже должен знать, что это за функция и как она определена. Если функция ещё не объявлена — Python выдаст ошибку и остановит выполнение кода.
Область видимости функций
Переменные в Python-программе имеют три области видимости. Область видимости отвечает за то, в каком месте кода можно использовать переменную, которая объявлена где-то в другом месте этой же программы.
Областей видимости при использовании функций в Python три.
Локальная область видимости включает переменные внутри функции. Эти переменные функция будет использовать в своих вычислениях, но без самой функции их использовать не получится.
Пример — локальная переменная x. Её нельзя будет использовать в других местах программы, только внутри этой функции (то есть там, где она объявлена):
# объявляем функцию с локальной переменной
def local_example():
# эта переменная видна только внутри функции
x = 10
Область объемлющей функции появляется при использовании вложенных функций, когда внутри одной функции существуют другие. Вложенная функция имеет доступ к переменным объемлющей функции, но не может их изменять напрямую, если не использовать ключевое слово nonlocal
.
Этот код:
# объявляем объемлющую функцию
def outer_function():
# переменная в области объемлющей функции
y = 20
# объявляем вложенную функцию
def inner_function():
# переменная в области объемлющей функции доступна
# для использования во вложенной функции, но не для изменения
print(f"Переменная из объемлющей области y = {y}")
# вызываем вложенную функцию
inner_function()
# вызываем объемлющую функцию
outer_function()
Выведет в консоли такой результат:
Переменная из объемлющей области y = 20
Если нужно сделать так, чтобы вложенная функция могла изменять значения в объемлющей, нужно отметить эти значения словом nonlocal
:
# объявляем объемлющую функцию
def outer_function():
# переменная в области объемлющей функции
y = 20
# объявляем вложенную функцию
def inner_function():
# указываем, что хотим изменить переменную объемлющей области
nonlocal y
# изменяем переменную
y = 30
print(f"Вложенная функция изменила y на {y}")
# вызываем вложенную функцию
inner_function()
print(f"После изменения: y = {y}")
# вызываем объемлющую функцию
outer_function()
Глобальная область видимости охватывает весь код. Переменные, объявленные вне функций, доступны везде, если их не перекрывают локальные переменные с тем же названием (в Python так можно).
Это похоже на объемлющую область, но только если переменная не в объемлющей области, а ещё на уровень выше — в основном теле кода. Функции имеют к ним доступ, но по умолчанию изменять не могут:
# глобальная переменная в теле кода
z = 50
# объявляем функцию
def global_example():
# используем доступ к глобальной переменной
print(f"Глобальная переменная z = {z}")
# вызываем функцию
global_example()
Результат в консоли:
Глобальная переменная z = 50
Если мы хотим изменить глобальную переменную, к ней можно получить доступ через ключевое слово global:
# глобальная переменная
z = 50
# проверяем переменную
print(f"До вызова функции z = {z}")
# объявляем функцию
def modify_global():
# указываем, что хотим изменить глобальную переменную
global z
# изменяем глобальную переменную
z = 100
# выводим результат изменения
print(f"Внутри функции z = {z}")
# вызываем функцию
modify_global()
# проверяем результат работы функции
print(f"После вызова функции z = {z}")
Запускаем:
До вызова функции z = 50
Внутри функции z = 100
После вызова функции z = 100
Аргументы функций
Всё, что мы передаём в функцию, называется аргументами. Вот что нужно знать про них.
Позиционные аргументы передаются функции в определённом порядке. Их позиция будет определять, какому параметру присвоено значение.
# объявляем функцию и передаём позиционные аргументы
def greet(name, age):
# пишем тело функции
print(f"Привет, {name}! Тебе {age} лет.")
# аргументы передадутся в таком порядке:
# name="Алиса", age=30
greet("Алиса", 30)
Если при передаче значений поменять их местами, то в name
вместо "Алиса"
функция запишет 30
, а вместо возраста — "Алиса"
.
Именованные аргументы передаются функции с указанием имени параметра. Это делает вызов функции более понятным и позволяет менять порядок аргументов.
# объявляем функцию и передаём позиционные аргументы
def greet(name, age):
# пишем тело функции
print(f"Привет, {name}! Тебе {age} лет.")
# при такой передаче аргументов их порядок можно менять
greet(age=30, name="Алиса")
Необязательные аргументы — аргументы, для которых указаны значения по умолчанию. Тогда можно вызывать функцию и не прописывать их повторно, если значение по умолчанию нам подходит. Если нужно какое-то другое значение, его нужно прописать как обычный позиционный или именованный аргумент:
# объявляем функцию и передаём для аргумента
# age значение по умолчанию
def greet(name, age=18):
# пишем тело функции
print(f"Привет, {name}! Тебе {age} лет.")
# вызываем функцию без передачи age
greet("Алиса")
# вызываем функцию и изменяем значение age
greet("Боб", 25)
Аргументы переменной длины (*args и **kwargs) нужны, если мы не знаем, сколько аргументов нам понадобится. Чем они отличаются:
*args
используется для передачи произвольного количества позиционных аргументов.**kwargs
используется для передачи произвольного количества именованных аргументов.
Вот как это будет выглядеть для позиционных аргументов:
# объявляем функцию с произвольным количеством позиционных аргументов
def sum_numbers(*args):
# пишем тело функции
total = sum(args)
# выводим на экран сумму позиционных аргументов
print(f"Сумма: {total}")
# вызываем функцию
sum_numbers(1, 2, 3, 4)
Такой код выведет в консоли:
Сумма: 10
Функция не проверяет аргументы автоматически. Если в список чисел попробовать передать строку, мы получим ошибку. Поэтому в реальном коде нужно предусмотреть вариант неправильного ввода и обработать его.
Вот пример для именованных аргументов. Значения сохраняются в виде словаря, который можно распаковать и вывести значения по отдельности.
# объявляем функцию с произвольным количеством именованных аргументов
def describe_person(**kwargs):
# пишем тело функции
for key, value in kwargs.items():
# выводим на экран словарь вида аргумент:значение
print(f"{key}: {value}")
# вызываем функцию
describe_person(name="Алиса", age=30, city="Москва")
Получаем:
name: Алиса
age: 30
city: Москва
Передача по значению и по ссылке означает два вида передачи аргументов в функцию и основным принципам работы Python.
Python устроен так, что при создании переменной он создаёт в памяти один объект. При копировании по умолчанию создаются не новые объекты, а новые ссылки, которые ведут на этот объект. Поэтому если изменить одну переменную, изменятся все остальные значения скопированных переменных.
Передача по ссылке означает, что в функцию тоже передаётся ссылка. Все изменения внутри функции отразятся на оригинальном объекте. Так работает передача всех аргументов в Python.
Передача по значению означает передачу в функцию полной копии объекта. В этом случае изменения внутри функции не повлияют на конечный объект.
Неизменяемые объекты не могут быть изменены внутри функции. Поэтому это может выглядеть как передача по значению, но на самом деле в Python всё передаётся по ссылке.
Словарь в качестве аргументов выглядит как переменная, которую передали в функцию через **kwargs
. Внутри функции этот аргумент можно распаковать. Для этого нужно знать, как выглядит словарь — тогда мы можем задать правильный алгоритм:
# объявляем функцию с позиционными аргументами
def describe_person(name, age, city):
# пишем тело функции
print(f"{name}, {age} лет, живёт в {city}")
# создаём словарь
person = {"name": "Алиса", "age": 30, "city": "Москва"}
# распаковываем словарь функцией
describe_person(**person)
При запуске Python распакует переменную со словарём и разложит значения по указанным в логике местам:
Алиса, 30 лет, живёт в Москва
Lambda-функции (анонимные)
Def в Python даёт начало обычным, классическим функциям, но есть и другие.
Lambda-функция — короткая анонимная функция, которая создаётся с помощью ключевого слова lambda
. Чаще всего они используются для создания простых функций, которые нужны в одном месте.
Как создать лямбда-функцию:
- написать ключевое слово
lambda
; - указать аргументы;
- поставить двоеточие;
- записать логику функции.
Пример, как может определяться простая lambda-функция подсчёта квадрата числа:
# объявляем переменную и присваиваем ей лямбда-функцию
square = lambda x: x ** 2
# выводим результат работы
print(square(5))
В консоли получаем ответ:
5
Напоследок: чистые функции и побочные эффекты
Чистая функция — функция, которая:
- Возвращает результат, зависящий только от её входных данных. Это ведёт к воспроизводимости — одинаковый ввод всегда приводит к одинаковому результату.
- Не изменяет внешние состояния — не изменяет глобальные переменные, не влияет на файлы или базы данных.
Побочные эффекты — это изменение глобальных данных, которые находятся за пределами функции. Эта возможность иногда необходима, но усложняет отладку программы и может приводить к неожиданному поведению.
Поэтому при создании функций везде, где это возможно, нужно делать их чистыми — так работать проще. Но это в идеале, а как на практике — зависит от разного.