В Python всё является объектом, поэтому функции могут принимать, и возвращать другие функции, а также одна функция может содержать другую. Всё вместе это лежит в основе таких понятий, как замыкание и декораторы, позволяющие расширять возможности функций.
Вложенная функция всегда имеет доступ к переменным, определенным в её внешней функции. И этот доступ – не просто временная передача значений, а постоянная ссылка на ячейки памяти, где эти значения хранятся.
Ключевой момент, который превращает этот механизм в замыкание, происходит, когда внешняя функция завершает свою работу. В обычной ситуации, когда функция заканчивает выполняться, все её локальные переменные уничтожаются. Однако это не происходит, если выполняются следующие ключевые условия:
- Внутренняя функция использует переменные внешней функции.
- Внешняя функция возвращает внутреннюю функцию.
В таком случае Python понимает, что вложенная функция всё ещё нуждается в переменных из внешней области, и эти переменные «замыкаются» вместе с вложенной функцией. Они не являются копией, а представляют собой прямую связь с исходными данными.
В Python работа с последовательностями данных часто строится на двух ключевых понятиях: итераторы и генераторы. Они позволяют эффективно обрабатывать большие объемы данных, избегая необходимости хранить всю последовательность в памяти.
Мы уже не единожды работали с итераторами, например, когда перебирали элементы коллекций в цикле for. Он неявно превращает итерируемый объект в итератор, обеспечивающий последовательный доступ к его элементам. Но также мы можем вручную преобразовать итерируемый объект в итератор и последовательно получить каждый его элемент с помощью функций iter() и next().
Функция iter() принимает итерируемый объект (например, список, кортеж или строку) и возвращает его итератор. Он не является копией переданной коллекции, а просто указывает на то, где в данный момент находится процесс итерации.
Функция next() принимает итератор и возвращает следующий элемент из его последовательности.
Помимо обычных функций, которые мы создаём с помощью ключевого слова def, в Python существуют так называемые анонимные функции, которые не имеют имени и представляют собой небольшие однострочные функции. По-другому их называют лямбда-функциями (от англ. lambda functions) из-за их связи с лямбда-исчислением, разработанным американским логиком и математиком Алонзо Чёрчем в 1930-х годах.
В этой системе функция представляется в виде выражения, которое начинается с греческой буквы лямбда λ. Например, функция, которая удваивает число, в лямбда-исчислении записывается как λx * 2, где:
- λ – указывает на начало определения функции;
- x – аргумент функции;
- 2 * x – тело функции.
Многие языки программирования, включая Python или JavaScript, заимствовали эту концепцию для создания небольших одноразовых функций.
В Python функции являются объектами, поэтому их можно присваивать переменным, возвращать из других функций и передавать в качестве аргументов другим функциям.
И если функция делает хотя бы одно из двух:
- принимает одну или несколько других функций в качестве аргументов;
- возвращает функцию как результат своей работы.
То такая функция называется функцией высшего порядка.
Одной из таких функций является функция sorted(), с которой мы познакомились, когда говорили о сортировке списков. Ей можно передать функцию, которая будет применяться к каждому элементу итерируемого объекта перед сортировкой:
vegetables = ["Баклажан", "Щавель", "Тыква", "Сельдерей", "Спаржа"]
print(sorted(vegetables, key=len)) # Сортировка по длине строки
# Вывод: ['Тыква', 'Щавель', 'Спаржа', 'Баклажан', 'Сельдерей']
Поэтому функция sorted() является функцией высшего порядка. Кроме неё в Python представлены функции map() и filter().
Рекурсия – это способ решения задачи, при котором функция вызывает сама себя, и такая функция называется рекурсивной.
Рекурсия позволяет, не используя цикл, разбить задачу на более мелкие, идентичные части и решать каждую из них с помощью того же самого алгоритма. Это похоже на ситуацию, когда для выполнения задания часть работы передаётся тому же исполнителю, но в более простом виде.
Рекурсия состоит из двух ключевых компонентов:
- Базовый случай – это условие, при котором функция прекращает вызывать саму себя. Он является точкой выхода и предотвращает бесконечные вызовы.
- Рекурсивный случай – это часть, где функция вызывает саму себя, но с измененными аргументами, чтобы каждый следующий вызов приближал функцию к базовому случаю.
В Python, как и во многих других языках программирования, у каждой переменной есть своя область видимости, которая определяет, в какой части программы к ней можно получить доступ. То есть переменная, определенная в одном месте, может быть невидима в другом.
С областями видимости тесно связано понятие пространства имён. Оно представляет собой систему, которая связывает имена (например, переменных и функций) с соответствующими объектами в программе. Python использует иерархическую структуру пространств имен для управления областями видимости.
Для определения, где Python ищет имя переменной или другого объекта, используется правило LEGB. Это аббревиатура определяет порядок поиска имен. Python ищет имя, начиная с самой внутренней (локальной) области и двигаясь наружу. Если имя найдено, поиск прекращается. Если оно не найдено ни в одной из областей, возникает исключение NameError.
Параметры – это переменные, которые функция использует для получения значений, переданных ей при вызове. Они позволяют функции работать с данными, переданными извне, делая её более гибкой и универсальной. Значения, которые передаются функции при её вызове, называются аргументами.
Например, встроенная функция sum() возвращает сумму всех элементов коллекции. При этом коллекция должна быть передана как первый аргумент функции, а значение параметра start, определяющее начальное значение суммы, может быть не указано вовсе, передано по имени или как второй аргумент функции:
numbers = [1, 2, 3]
print(sum(numbers))
# Вывод: 6
print(sum(numbers, start=10))
# Вывод: 16
print(sum(numbers, 10))
# Вывод: 16
Таким образом, значения параметров, то есть аргументы, могут быть переданы функции по позиции или по имени. А сами параметры функции делятся на обязательные и необязательные. Обязательные параметры должны быть переданы при каждом вызове функции, в то время как необязательные параметры могут быть опущены, и для них будет использовано значение по умолчанию.
Python предоставляет большой набор встроенных функций, некоторыми из которых мы уже пользовались, например, print() для вывода данных на экран или len() для определения количества элементов в коллекции. Исходный код большинства встроенных функций написан на том же языке, что и сам интерпретатор Python. И если наиболее используемым интерпретатором является CPython, написанный на C, то и код многих функций написан на C. Но существует поддержка и других языков, например, интерпретатор Jython использует язык программирования Java, а IronPython – C#.
Функции, написанные один раз, можно использовать бесконечное количество раз без необходимости повторного написания их кода. Это позволяет следовать одному из основополагающих принципов программирования DRY (от англ. Don’t Repeat Yourself – Не повторяйтесь), который заключается в том, что следует избегать избыточного дублирования кода.
В случае встроенных функций, нам не нужно знать, как именно они реализованы, для того чтобы их использовать. У каждой функции есть имя, по которому её можно вызвать, она может принимать определённые параметры и совершать определённые действия.
Иногда возникает необходимость создать новую коллекцию на основе уже существующей. Представьте, у вас есть список чисел, и вам нужно получить новый список, содержащий только квадраты чётных чисел из этого списка.
Один из способов сделать это – перебрать все элементы исходного списка в цикле for и добавить в новый список квадраты тех чисел, которые делятся на 2 без остатка:
numbers = [1, 2, 3, 4, 5]
squared_numbers = []
for n in numbers:
if n % 2 == 0:
squared_numbers.append(n ** 2)
print(squared_numbers)
# Вывод: [4, 16]
Этот код отлично справляется со своей задачей, но в Python существует более компактный способ создания списков, называемый генератором списка (или списочным выражением). Он позволяет создать новый список всего в одну строку кода.
Списки и кортежи подходят для хранения последовательности элементов, но что, если нам удобнее обращаться к элементам по какому-то имени, а не по порядковому номеру? Для этого в Python предназначены словари. Вместо того чтобы хранить элементы по порядку, словари хранят пары «ключ-значение». Они похожи на настоящие словари, где у каждого слова (ключа) есть свое определение (значение).
Ключи словаря должны быть уникальными и представлены неизменяемым типом данных, например, числом, строкой, кортежем или даже замороженным множеством. На значения словаря никакие ограничения не накладываются.
В самом простом случае словарь создаётся с помощью фигурных скобок, внутри которых через запятую перечисляются пары ключ: значение:
phone_book = {
"Мастер": "+79315555555",
"Маргарита": "+79637777777"
}