Функции первого класса, функции высшего порядка и замыкания в Python – объяснение с примерами кода
В современном программировании важно понимать такие понятия, как функции первого класса, функции высшего порядка и замыкания. Эти идеи помогают нам писать гибкий и эффективный код. Они также являются строительными блоками для многих продвинутых техник кодирования.
Первоклассные функции и функции высшего порядка позволяют нам рассматривать функции как переменные, что делает наш код более мощным и пригодным для повторного использования. Замыкания делают еще один шаг вперед, позволяя функциям запоминать переменные из своей области видимости.
В этом уроке мы рассмотрим эти концепции и объясним, как они связаны между собой, а также приведем практические примеры кодинга, иллюстрирующие их использование.
Для полного понимания замыканий необходимо сначала разобраться в концепциях функций первого класса и функций высшего порядка. Эти понятия лежат в основе многих продвинутых паттернов программирования и являются фундаментальными для функционирования замыканий. Давайте рассмотрим эти понятия и их взаимосвязь.
Функции первого класса
В программировании считается, что язык имеет первоклассные функции, если в нём функции рассматриваются как граждане первого класса. Это означает, что функции в таком языке могут быть:
- Присваиваются переменным
- Передаются в качестве аргументов другим функциям
- Возвращаются из других функций
Проще говоря, с первоклассными функциями можно работать так же, как с любой другой переменной или объектом в языке. Давайте рассмотрим эти возможности на примерах кодирования.
Присвоение функций переменным
Обычно мы вызываем функцию и присваиваем её результат переменной. Например:
def add(a, b):
return a + b
result = add(3, 4)
print(add) # Prints the function object
print(result) # Prints 7
Здесь add
– это функция, которая возвращает сумму двух чисел. Когда мы вызываем add(3, 4)
, она возвращает 7
, которое присваивается переменной result
. Вывод этого кода будет таким:
<function add at 0x...>
7
Теперь присвоим функцию add
переменной sum_function
без её вызова (то есть, без круглых скобок).
sum_function = add
Печать add
и sum_function
показывает, что они оба ссылаются на один и тот же объект функции:
print(add) # Outputs: <function square at 0x...>
print(sum_function) # Outputs: <function square at 0x...>
Это демонстрирует, что функции можно присваивать переменным так же, как и любой другой тип данных. Теперь мы можем использовать sum_function
так же, как и оригинальную функцию add
, и даже удалить оригинальное имя функции add
, и функция все равно будет доступна через sum_function
.
del add
print(sum_function(3, 4)) # Output: 7
Передача функций в качестве аргументов
Еще один аспект первоклассных функций – возможность передавать их в качестве аргументов другим функциям. Это позволяет добиться большей гибкости и модульности кода.
Проиллюстрируем это на примере пользовательской функции map
. Функция map
применяет заданную функцию к каждому элементу списка (или массива) и возвращает новый список с результатами.
def double(n):
return n * 2
def map_function(func, values):
result = []
for value in values:
result.append(func(value))
return result
# Use the custom map function
doubled_values = map_function(double, [3, 6, 9, 12, 15])
print(doubled_values) # Output: [6, 12, 18, 24, 30]
В этом примере функция double
принимает число n
и возвращает его двойное значение. Функция map_function
принимает функцию func
и список values
, применяет func
к каждому элементу в values
и возвращает новый список с результатами.
Когда map_function
вызывается с double
и списком [3, 6, 9, 12, 15]
, она применяет функцию double
к каждому элементу списка, в результате чего получается [6, 12, 18, 24, 30]
. Это демонстрирует, как функции можно передавать в качестве аргументов для создания гибких и многократно используемых шаблонов кода.
Обратите внимание, что при передаче функции мы не указываем круглые скобки (то есть, double
вместо double()
), что говорит о том, что мы передаем саму функцию, а не результат её вызова.
Возврат функций из других функций
Возврат функций из других функций – еще одна важная характеристика первоклассных функций. Эта концепция позволяет создавать более сложный и модульный код, который часто используется в таких сценариях, как создание настраиваемых функций или закрытий.
Чтобы проиллюстрировать это, давайте рассмотрим практический пример, в котором функция возвращает другую функцию:
def create_multiplier(factor):
"""Returns a function that multiplies its input by the given factor."""
def multiplier(x):
return x * factor
return multiplier
# Create specific multiplier functions
double = create_multiplier(2)
triple = create_multiplier(3)
# Use the created functions
print(double(5)) # Output: 10
print(triple(5)) # Output: 15
В этом примере функция create_multiplier
принимает параметр factor
и возвращает другую функцию multiplier
. Эта функция multiplier
при вызове с аргументом x
возвращает произведение x
и factor
.
При вызове create_multiplier
с параметром 2
возвращается функция, умножающая аргумент на 2
. Аналогично, при вызове с параметром 3
возвращается функция, умножающая аргумент на 3
. Эти возвращенные функции (double
и triple
) могут быть вызваны с аргументами для выполнения умножения. Например, double(5)
возвращает 10
, а triple(5)
– 15
.
В этом и заключается суть закрытия, когда возвращаемая функция (multiplier
) сохраняет доступ к переменной (factor
) из своей объемлющей области, даже после завершения выполнения внешней функции (create_multiplier
). Это позволяет созданным функциям (double
и triple
) помнить и использовать значение factor
, с которым они были созданы.
Функции высшего порядка
Функция высшего порядка – это функция, которая работает над другими функциями. В частности, функция высшего порядка может:
- Принимать одну или несколько функций в качестве аргументов
- Возвращать функцию в качестве результата
По сути, функции высшего порядка используют функции первого класса, принимая их в качестве аргументов или возвращая их в качестве результатов, что позволяет выполнять мощные операции над самими функциями. Мы уже видели примеры и того, и другого:
- В нашем примере "Передача функций в качестве аргументов"
map_function
– это функция высшего порядка, потому что она принимает функцию (double
) в качестве аргумента. - В нашем примере "Возврат функций из других функций" функция
create_multiplier
является функцией высшего порядка, поскольку возвращает в качестве результата другую функцию (multiplier
).
Прежде чем перейти к закрытиям, давайте быстро обсудим лямбда-функции, поскольку они добавляют еще один уровень гибкости и позволяют создавать более лаконичный и выразительный код.
Лямбда-функции
Лямбда-функции в Python, также известные как анонимные функции, – это небольшие функции, определяемые с помощью ключевого слова lambda
. Они часто используются для решения краткосрочных задач, которые не требуют полного определения функции с помощью def
.
По сути, они являются синтаксическим сахаром для определения небольших функций. Синтаксис лямбда-функции следующий:
lambda arguments: expression
Лямбда-функции могут передаваться в качестве аргументов в функции более высокого порядка или возвращаться из них, что делает их универсальными инструментами в функциональном программировании.
Пример: Использование лямбда-функций в пользовательской функции Map
Ранее мы рассмотрели пример с функцией map_function
. Давайте посмотрим, как можно добиться той же функциональности с помощью лямбда-функции:
def map_function(func, values):
return [func(value) for value in values]
# Using a lambda function as the argument
doubled_values = map_function(lambda n: n * 2, [3, 6, 9, 12, 15])
print(doubled_values) # Output: [6, 12, 18, 24, 30]
В этом примере лямбда-функция lambda n: n * 2
передается непосредственно в map_function
, что устраняет необходимость в отдельном определении функции double
.
Пример: Создание мультипликативных функций с помощью лямбд
Обратившись к примеру create_multiplier
, мы можем использовать лямбда-функцию для multiplier
:
def create_multiplier(factor):
return lambda x: x * factor
# Create specific multiplier functions
double = create_multiplier(2)
triple = create_multiplier(3)
# Use the created functions
print(double(5)) # Output: 10
print(triple(5)) # Output: 15
Здесь create_multiplier
возвращает лямбда-функцию, которая умножает входные данные на указанный factor
. Это компактный и выразительный способ определить ту же самую функциональность.
Взаимозависимость функций высшего порядка и функций первого порядка
Функции высшего порядка – прямое следствие того, что функции являются гражданами первого класса. Если к функциям можно относиться как к любым другим значениям, то из этого естественно следует, что вы можете передавать функции другим функциям и возвращать их из других функций.
Таким образом, можно сказать, что функции высшего порядка неотъемлемо опираются на концепцию первоклассных функций. Без функций первого класса мы не можем иметь функции высшего порядка, потому что последние зависят от возможности передавать и возвращать функции.
Давайте разберемся в этом на других примерах:
Пример: Функция высшего порядка, принимающая функцию в качестве аргумента (функцию первого класса)
def apply_operation(operation, x, y):
return operation(x, y)
# Functions to pass as arguments
def add(x, y):
return x + y
def multiply(x, y):
return x * y
# Using the higher-order function
result_add = apply_operation(add, 3, 4)
result_multiply = apply_operation(multiply, 3, 4)
print(result_add) # Output: 7
print(result_multiply) # Output: 12
В этом примере функция apply_operation
является функцией высшего порядка, поскольку принимает в качестве аргумента другую функцию (operation
). Функции add
и multiply
являются функциями первого класса, потому что их можно передавать в качестве аргументов другим функциям.
Функция apply_operation
принимает три параметра: функцию (operation
) и два целых числа (x
и y
). Она возвращает результат применения функции operation
к x
и y
.
Вызов функции apply_operation(add, 3, 4)
возвращает 7 – результат сложения 3 и 4. Аналогично, вызов apply_operation(multiply, 3, 4)
возвращает 12 - результат умножения 3 и 4. Это демонстрирует гибкость и возможность повторного использования функций высшего порядка, показывая, как мы можем выполнять различные операции над одним и тем же набором входных данных.
Пример: Функция высшего порядка, возвращающая другую функцию (функцию первого класса)
def discount_applier(discount_rate):
def apply_discount(price):
return price - (price * discount_rate / 100)
return apply_discount
# Creating closures with different discount rates
holiday_discount = discount_applier(20)
member_discount = discount_applier(15)
# Applying the discounts
print(holiday_discount(100)) # Output: 80.0
print(member_discount(100)) # Output: 85.0
В этом примере discount_applier
принимает параметр discount_rate
и возвращает новую функцию apply_discount
. Это делает ее "функцией высшего порядка", а apply_discount
считается "функцией первого класса", поскольку она определяется внутри discount_applier
и возвращается для последующего использования.
Эта функция apply_discount
при вызове с аргументом price
возвращает цену со скидкой, рассчитанную с помощью discount_rate
.
Когда discount_applier
вызывается с коэффициентом скидки 20, он возвращает функцию, которая применяет скидку 20% к своему аргументу. Аналогично, при вызове с коэффициентом скидки 15 возвращается функция, применяющая скидку 15 %. Эти возвращенные функции (holiday_discount
и member_discount
) можно использовать для применения соответствующих скидок.
Вызов holiday_discount(100)
возвращает 80.0, применяя к 100 скидку 20%. Вызов member_discount(100)
возвращает 85,0, применяя скидку 15 %.
Эти примеры иллюстрируют, как функции высшего порядка позволяют создавать гибкие, многократно используемые и модульные модели кода, используя возможности функций первого класса. Они лежат в основе многих продвинутых техник программирования, включая закрытие, и необходимы для написания выразительного и мощного кода.
Замыкания
Замыкание – это функция во многих языках программирования, включая Python, которая позволяет функции помнить и обращаться к переменным из объемлющей области даже после завершения выполнения внешней функции.
Проще говоря, замыкание – это внутренняя функция, которая имеет доступ к переменным из содержащей ее (или внешней) функции даже после того, как внешняя функция завершила свое выполнение.
Давайте рассмотрим несколько примеров, чтобы понять, как работают замыкания в Python:
Базовая вложенная функция с немедленным выполнением
def outer_scope():
name = 'Sam'
city = 'New York'
def inner_scope():
print(f"Hello {name}, Greetings from {city}")
return inner_scope()
outer_scope()
В этом примере функция outer_scope
определяет две локальные переменные: name
и city
. Затем она определяет и сразу же вызывает inner_scope
, которая печатает приветствие, используя переменные name
и city
из вложенной области видимости.
Когда вызывается outer_scope
, запускается вложенная функция inner_scope
, которая выводит приветствие: "Hello Sam, Greetings from New York".
Возврат внутренней функции
Теперь давайте изменим пример, чтобы вернуть внутреннюю функцию, не выполняя её сразу:
def outer_scope():
name = 'Sam'
city = 'New York'
def inner_scope():
print(f"Hello {name}, Greetings from {city}")
return inner_scope
# Assigning the inner function to a variable
greeting_func = outer_scope()
# Calling the inner function
greeting_func()
Здесь outer_scope
определяет name
и city
как переменные, аналогично приведенному выше примеру. Затем он определяет и возвращает функцию inner_scope
, но на этот раз без ее вызова (то есть inner_scope
вместо inner_scope()
).
Когда выполняется greeting_func = outer_scope()
, она присваивает функцию inner_scope
, возвращенную outer_scope
, функции greeting_func
.
Теперь в greeting_func
хранится ссылка на функцию inner_scope
. Вызов greeting_func()
запускает inner_scope
, которая печатает: "Hello Sam, Greetings from New York".
Несмотря на то что к моменту вызова greeting_func()
outer_scope
уже завершила выполнение, функция inner_scope
(на которую теперь ссылается greeting_func
) сохраняет доступ к переменным name
и city
из своей объемлющей области. Именно это и делает ее замыканием – она "замыкает" на себя переменные из содержащей её области видимости.
Использование замыканий с параметрами
Чтобы продемонстрировать возможности замыканий, давайте создадим более динамичный пример, добавив параметры в функцию outer_scope
:
def outer_scope(name, city):
def inner_scope():
print(f"Hello {name}, Greetings from {city}")
return inner_scope
# Creating closures with different names and locations
greet_priyanshu = outer_scope('Dr Priyanshu', 'Jaipur')
greet_sam = outer_scope('Sam', 'New York')
# Executing the closures
greet_priyanshu() # Output: Hello Dr Priyanshu, Greetings from Jaipur
greet_sam() # Output: Hello Sam, Greetings from New York
Здесь функция outer_scope
принимает в качестве параметров name
и city
. Внутри outer_scope
определяется функция inner_scope
, которая печатает приветствие, используя name
и city
. Вместо вызова inner_scope
, outer_scope
возвращает саму функцию inner_scope
.
Когда outer_scope
вызывается с определенными аргументами, она создает и возвращает закрытие, в котором отражены эти аргументы. Например, greet_priyanshu
– это замыкание, которое запоминает Dr Priyanshu
и Jaipur
, а greet_sam
– Sam
и New York
. Когда эти замыкания вызываются, они выдают соответствующие приветствия.
Несмотря на то что outer_scope
завершила выполнение в обоих случаях, функции inner_scope
(теперь greet_priyanshu
и greet_sam
) сохраняют доступ к соответствующим переменным name
и city
из своих объемлющих диапазонов, демонстрируя поведение закрытия.
При желании вы также можете использовать лямбда-функцию вместо нашей внутренней функции (inner_scope
), как показано здесь:
Используя лямбда-функцию, мы достигаем того же результата более лаконичным способом. Замыкания, созданные outer_scope
, по-прежнему сохраняют доступ к переменным name
и city
, демонстрируя то же самое поведение замыкания.
Реальные применения замыканий
Теперь давайте посмотрим на некоторые практические применения замыканий в реальном программировании. Вот несколько сценариев, в которых часто используются замыкания:
Обработчики событий в веб-разработке (пример js, но важный пример использования)
В JavaScript замыкания часто используются для обработки событий, таких как нажатие кнопки.
<html>
<head>
<title>Button Handler Example</title>
</head>
<body>
<button id="button1">Button 1</button>
<button id="button2">Button 2</button>
<script>
function createButtonHandler(buttonName) {
return function() {
alert(`Button ${buttonName} clicked!`);
};
}
const button1 = document.getElementById('button1');
const button2 = document.getElementById('button2');
button1.onclick = createButtonHandler('Button 1');
button2.onclick = createButtonHandler('Button 2');
</script>
</body>
</html>
Пояснение:
createButtonHandler
– это функция высшего порядка, которая принимает в качестве аргументаbuttonName
и возвращает функцию (замыкание).- Возвращаемая функция (замыкание) захватывает переменную
buttonName
из своей лексической области видимости.\ - При нажатии на кнопку вызывается соответствующая замыкающая функция, которая имеет доступ к
buttonName
, переданному при создании обработчика.
Сохранение состояния в GUI-приложениях
В Python замыкания можно использовать для сохранения состояния в приложениях с графическим интерфейсом пользователя (GUI), например, в приложениях, созданных с помощью Tkinter.
import tkinter as tk
def create_counter():
count = 0
def counter():
nonlocal count
count += 1
print(f'Button clicked {count} times')
return counter
root = tk.Tk()
root.title('Counter Example')
counter = create_counter()
button = tk.Button(root, text='Click me', command=counter)
button.pack(pady=20)
root.mainloop()
Пояснение:
create_counter
– это функция высшего порядка, которая инициализируетcount
в 0 и определяет вложенную функциюcounter
.- Функция
counter
представляет собой замыкание, которое захватывает переменную count из объемлющей области видимости. - Ключевое слово
nonlocal
позволяет замыканию изменять переменнуюcount
. - При каждом нажатии на кнопку вызывается функция
counter
, которая увеличивает и печатает значениеcount
.
Некоторые другие приложения включают в себя:
Создание декораторов
Декораторы в Python – это мощный инструмент для изменения или расширения поведения функций и методов. Замыкания являются базовым механизмом для реализации декораторов.
Сокрытие данных и инкапсуляция
Замыкания можно использовать для создания приватных переменных и методов в функции, доступ к которым и их изменение возможны только во внутренней функции. Это позволяет достичь инкапсуляции в Python.
Заключение
Функции первого класса позволяют нам относиться к функциям как к любым другим объектам или типам данных, обеспечивая гибкость:
- Присваивать их переменным
- Передавать их в качестве аргументов другим функциям
- Возвращать их из других функций
Эта функциональность является основополагающим аспектом многих продвинутых техник программирования, включая закрытия. Понимание функций первого класса – первый шаг к освоению этих более сложных концепций.
С другой стороны, функции высшего порядка делают еще один шаг вперед. Они позволяют не только обращаться с функциями как с данными, но и использовать их для создания новых функций или изменения поведения других функций. Это открывает путь к более сложным и гибким способам решения проблем в программировании.
Они позволяют выполнять такие операции, как:
- Принятие функций в качестве аргументов/параметров
- Возвращение функций в качестве результатов
Хотя все функции высшего порядка включают в себя функции первого класса, не все функции первого класса обязательно являются функциями высшего порядка.
Замыкания, в общем, позволяют внутренним функциям получать доступ к переменным из их объемлющей области.
Свободные переменные – это переменные, используемые во внутренней функции, но определенные во внешней функции.
Практическое использование замыканий включает в себя сценарии, в которых вам нужно, чтобы функции сохраняли состояние даже после завершения выполнения внешней функции.