Python & AI Tutorials Logo
Программирование Python

23. Функции первого класса и функциональные техники

В предыдущих главах мы изучили, как определять и вызывать функции, работать с параметрами и аргументами, а также понимать область видимости переменных. Теперь мы рассмотрим мощную возможность, которая выделяет Python: функции — это объекты первого класса. Это означает, что с функциями можно обращаться как с любым другим значением: хранить в переменных, передавать в качестве аргументов другим функциям и возвращать из функций.

Эта возможность открывает элегантные техники программирования, которые делают код более гибким, переиспользуемым и выразительным. Мы рассмотрим, как использовать функции первого класса на практических примерах, разберём замыкания (функции, которые «помнят» своё окружение), применим lambda-выражения для кратких определений функций и используем встроенные функции вроде map(), filter(), any() и all() для эффективной работы с коллекциями.

23.1) Функции как объекты первого класса

23.1.1) Что означает «первый класс»

В Python функции — это объекты первого класса, а значит, их можно:

  • Присваивать переменным
  • Хранить в структурах данных (списках, словарях и т. д.)
  • Передавать как аргументы другим функциям
  • Возвращать как значения из других функций

Это отличается от некоторых языков программирования, где функции имеют особый статус и ими нельзя манипулировать как обычными значениями. В Python функция — это просто ещё один тип объекта, похожий на целые числа, строки или списки.

Посмотрим на это в действии:

python
# Определим простую функцию
def greet(name):
    return f"Hello, {name}!"
 
# Присвоим функцию переменной
say_hello = greet
 
# Вызовем функцию через новую переменную
message = say_hello("Alice")
print(message)  # Output: Hello, Alice!
 
# Проверим, что оба имени ссылаются на одну и ту же функцию
print(greet)      # Output: <function greet at 0x...>
print(say_hello)  # Output: <function greet at 0x...>
print(greet is say_hello)  # Output: True

Обратите внимание: когда мы пишем say_hello = greet, мы не вызываем функцию (нет круглых скобок). Мы создаём новое имя, которое ссылается на тот же объект функции. Теперь и greet, и say_hello указывают на одну и ту же функцию, что можно проверить с помощью оператора is.

23.1.2) Хранение функций в структурах данных

Поскольку функции — это объекты, мы можем хранить их в списках, словарях или любой другой коллекции:

python
# Калькулятор с операциями, сохранёнными в словаре
def add(x, y):
    return x + y
 
def subtract(x, y):
    return x - y
 
def multiply(x, y):
    return x * y
 
def divide(x, y):
    return x / y
 
# Сохраним функции в словаре
operations = {
    '+': add,
    '-': subtract,
    '*': multiply,
    '/': divide
}
 
# Используем словарь для выполнения вычислений
num1 = 10
num2 = 5
operator = '*'
 
result = operations[operator](num1, num2)
print(f"{num1} {operator} {num2} = {result}")  # Output: 10 * 5 = 50

Этот шаблон чрезвычайно полезен при построении гибких систем. Вместо того чтобы писать длинные цепочки операторов if-elif, чтобы выбрать, какую функцию вызвать, мы можем найти подходящую функцию в словаре и вызвать её напрямую.

23.2) Передача функций в качестве аргументов

23.2.1) Базовая концепция

Одно из самых мощных применений функций первого класса — передавать их как аргументы другим функциям. Это позволяет писать гибкий, переиспользуемый код, который может работать с разным поведением.

Вот простой пример:

python
# Функция, которая применяет другую функцию к значению
def apply_operation(value, operation):
    """Применить функцию operation, полученную как параметр, к value."""
    return operation(value)
 
# Разные операции
def double(x):
    return x * 2
 
def square(x):
    return x * x
 
def negate(x):
    return -x
 
# Используем одну и ту же функцию apply_operation с разными операциями
number = 5
print(apply_operation(number, double))   # Output: 10
print(apply_operation(number, square))   # Output: 25
print(apply_operation(number, negate))   # Output: -5

Функция apply_operation не знает и не заботится о том, какую именно операцию она выполняет. Она просто вызывает любую функцию, которую ей передали. Такое разделение ответственности делает код более модульным и его проще расширять.

23.2.2) Обработка коллекций с пользовательскими функциями

Распространённый шаблон — обрабатывать каждый элемент коллекции, используя функцию, переданную как аргумент:

python
# Обрабатываем каждый элемент списка с помощью заданной функции
def process_list(items, processor):
    """Применить функцию processor к каждому элементу в списке."""
    results = []
    for item in items:
        results.append(processor(item))
    return results
 
# Разные функции обработки
def uppercase(text):
    return text.upper()
 
def add_exclamation(text):
    return text + "!"
 
def get_length(text):
    return len(text)
 
# Обрабатываем один и тот же список разными способами
words = ["hello", "world", "python"]
 
print(process_list(words, uppercase))        # Output: ['HELLO', 'WORLD', 'PYTHON']
print(process_list(words, add_exclamation))  # Output: ['hello!', 'world!', 'python!']
print(process_list(words, get_length))       # Output: [5, 5, 6]

Этот шаблон настолько полезен, что Python предоставляет встроенные функции вроде map() и filter(), которые работают таким образом (мы рассмотрим их в разделе 23.6).

23.2.3) Сортировка с передачей key-функции (краткое введение)

Функция Python sorted() принимает параметр key — функцию, которая определяет, как сравнивать элементы:

python
# Сортируем студентов по разным критериям
students = [
    {"name": "Alice", "grade": 85, "age": 20},
    {"name": "Bob", "grade": 92, "age": 19},
    {"name": "Charlie", "grade": 78, "age": 21},
    {"name": "Diana", "grade": 95, "age": 20}
]
 
# Функция для извлечения оценки
def get_grade(student):
    return student["grade"]
 
# Функция для извлечения имени
def get_name(student):
    return student["name"]
 
# Сортировка по оценке (по возрастанию)
by_grade = sorted(students, key=get_grade)
print("Sorted by grade:")
for student in by_grade:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Charlie: 78
#   Alice: 85
#   Bob: 92
#   Diana: 95
 
# Сортировка по имени (в алфавитном порядке)
by_name = sorted(students, key=get_name)
print("\nSorted by name:")
for student in by_name:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Alice: 85
#   Bob: 92
#   Charlie: 78
#   Diana: 95

Функция key вызывается один раз для каждого элемента, а её возвращаемое значение используется для сравнения. Это намного гибче, чем необходимость писать кастомную логику сортировки.

Этот шаблон передачи функций для настройки поведения чрезвычайно распространён в Python. Более продвинутые техники сортировки мы рассмотрим в главе 38.

23.3) Возврат функций из функций

23.3.1) Функции, которые создают функции

Так же как мы можем передавать функции как аргументы, мы можем и возвращать функции из других функций. Это позволяет динамически создавать специализированные функции:

python
# Функция, которая создаёт и возвращает новую функцию
def create_multiplier(factor):
    """Создать функцию, которая умножает на заданный factor."""
    def multiplier(x):
        return x * factor
    return multiplier
 
# Создаём специализированные функции-умножители
double = create_multiplier(2)
triple = create_multiplier(3)
times_ten = create_multiplier(10)
 
# Используем созданные функции
print(double(5))      # Output: 10
print(triple(5))      # Output: 15
print(times_ten(5))   # Output: 50

Что здесь происходит? Функция create_multiplier определяет внутреннюю функцию с именем multiplier и возвращает её. Каждый раз, когда мы вызываем create_multiplier с разным множителем, мы получаем новую функцию, которая «помнит» именно этот множитель. Это наш первый взгляд на замыкания, которые мы подробно рассмотрим в следующем разделе.

23.3.2) Создание настраиваемых валидаторов

Возврат функций особенно полезен для создания настраиваемых функций валидации или обработки:

python
# Динамически создаём валидаторы диапазона
def create_range_validator(min_value, max_value):
    """Создать функцию, которая проверяет, находится ли число в диапазоне."""
    def validator(number):
        return min_value <= number <= max_value
    return validator
 
# Создаём конкретные валидаторы
is_valid_age = create_range_validator(0, 120)
is_valid_percentage = create_range_validator(0, 100)
is_room_temperature = create_range_validator(15, 30)
 
# Используем валидаторы
age = 25
print(f"Is {age} a valid age? {is_valid_age(age)}")  # Output: True
 
temp = 22
print(f"Is {temp}°C room temperature? {is_room_temperature(temp)}")  # Output: True
 
score = 150
print(f"Is {score} a valid percentage? {is_valid_percentage(score)}")  # Output: False

23.4) Понимание замыканий: функции, которые помнят

23.4.1) Что такое замыкание?

Замыкание(closure) — это функция, которая «помнит» переменные из области видимости, где она была создана, даже после того, как выполнение этой области видимости завершилось. В примерах из раздела 23.3 мы уже использовали замыкания, не называя их явно.

Рассмотрим, как работают замыкания:

python
def create_counter(start=0):
    """Создать функцию-счётчик, которая помнит своё значение."""
    count = start  # Эта переменная «захватывается» замыканием
    
    def counter():
        nonlocal count  # Доступ к захваченной переменной
        count += 1
        return count
    
    return counter
 
# Создаём два независимых счётчика
counter1 = create_counter(0)
counter2 = create_counter(100)
 
# Каждый счётчик хранит собственное значение
print(counter1())  # Output: 1
print(counter1())  # Output: 2
print(counter1())  # Output: 3
 
print(counter2())  # Output: 101
print(counter2())  # Output: 102
 
print(counter1())  # Output: 4 (counter1 is independent of counter2)

Внутренняя функция counter образует замыкание над переменной count. Хотя create_counter уже завершила выполнение, возвращённая функция counter по-прежнему имеет доступ к count. Каждый вызов create_counter создаёт новое независимое замыкание со своей переменной count.

23.4.2) Как замыкания захватывают переменные

Когда функция определяется внутри другой функции, она может получать доступ к переменным из области видимости внешней функции. Эти переменные «захватываются» и остаются доступными даже после того, как внешняя функция вернётся:

Когда Python создаёт внутреннюю функцию, он сохраняет не только код функции — он также сохраняет ссылки на любые переменные из внешней функции, которые использует внутренняя функция. Этот процесс называется «захватом» переменных.

python
def create_greeter(greeting):
    """Создать функцию приветствия с пользовательским приветствием."""
    def greet(name):
        return f"{greeting}, {name}!"
    return greet
 
# Создаём разные функции приветствия
say_hello = create_greeter("Hello")
say_hi = create_greeter("Hi")
say_bonjour = create_greeter("Bonjour")
 
# Каждая функция приветствия «помнит» своё конкретное приветствие
print(say_hello("Alice"))    # Output: Hello, Alice!
print(say_hi("Bob"))         # Output: Hi, Bob!
print(say_bonjour("Claire")) # Output: Bonjour, Claire!

Параметр greeting захватывается замыканием. У каждой функции приветствия есть своё захваченное значение greeting, которое она использует при каждом вызове.

23.4.3) Практическое применение: конфигурационные функции

Замыкания отлично подходят для создания функций с заранее настроенным поведением:

python
# Создаём калькуляторы цен с разными налоговыми ставками
def create_price_calculator(tax_rate):
    """Создать калькулятор, который применяет конкретную tax_rate."""
    def calculate_total(price):
        tax = price * tax_rate
        return price + tax
    return calculate_total
 
# Создаём калькуляторы для разных регионов
us_calculator = create_price_calculator(0.07)    # 7% tax
uk_calculator = create_price_calculator(0.20)    # 20% VAT
japan_calculator = create_price_calculator(0.10) # 10% consumption tax
 
# Рассчитываем цены в разных регионах
item_price = 100
 
print(f"US total: ${us_calculator(item_price):.2f}")      # Output: US total: $107.00
print(f"UK total: £{uk_calculator(item_price):.2f}")      # Output: UK total: £120.00
print(f"Japan total: ¥{japan_calculator(item_price):.2f}") # Output: Japan total: ¥110.00

23.4.4) Когда использовать замыкания

Замыкания особенно полезны, когда вам нужно:

  • Создавать функции с заранее настроенным поведением
  • Хранить состояние между вызовами функций без использования классов
  • Реализовывать callback-функции, которым нужно помнить контекст
  • Создавать фабрики функций, которые производят специализированные функции

23.5) Использование lambda для коротких анонимных функций

23.5.1) Что такое lambda-выражения?

lambda-выражение создаёт маленькую анонимную функцию — функцию без имени. Lambda-выражения полезны, когда вам нужна простая функция на короткое время и вы не хотите формально определять её через def.

Синтаксис:

python
lambda parameters: expression

Lambda принимает параметры (как обычная функция) и возвращает результат вычисления выражения. Вот простой пример:

python
# Обычная функция
def add(x, y):
    return x + y
 
# Эквивалентное lambda-выражение
add_lambda = lambda x, y: x + y
 
# Оба варианта работают одинаково
print(add(3, 5))        # Output: 8
print(add_lambda(3, 5)) # Output: 8

Lambda-выражения ограничены одним выражением — они не могут содержать операторы вроде if, for или несколько строк кода. Это ограничение делает их простыми и сфокусированными.

23.5.2) Lambda-выражения как аргументы

Lambda-выражения особенно хороши, когда нужно передать простую функцию как аргумент и не хочется определять отдельную именованную функцию:

python
# Сортируем студентов по оценке с использованием lambda
students = [
    {"name": "Alice", "grade": 85},
    {"name": "Bob", "grade": 92},
    {"name": "Charlie", "grade": 78},
    {"name": "Diana", "grade": 95}
]
 
# Вместо определения отдельной функции:
# def get_grade(student):
#     return student["grade"]
# sorted_students = sorted(students, key=get_grade)
 
# Можно использовать lambda напрямую:
sorted_students = sorted(students, key=lambda student: student["grade"])
 
print("Students sorted by grade:")
for student in sorted_students:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Charlie: 78
#   Alice: 85
#   Bob: 92
#   Diana: 95

Так получается более лаконично, когда функция проста и используется только один раз. Lambda lambda student: student["grade"] эквивалентна функции, которая принимает студента и возвращает его оценку.

23.5.3) Lambda с несколькими параметрами

Lambda-выражения могут принимать несколько параметров, как и обычные функции:

python
# Операции калькулятора с использованием lambda
operations = {
    'add': lambda x, y: x + y,
    'subtract': lambda x, y: x - y,
    'multiply': lambda x, y: x * y,
    'divide': lambda x, y: x / y if y != 0 else "Error"
}
 
# Используем lambda-выражения
print(operations['add'](10, 5))       # Output: 15
print(operations['multiply'](10, 5))  # Output: 50
print(operations['divide'](10, 0))    # Output: Error

Обратите внимание: мы можем использовать условное выражение (x / y if y != 0 else "Error") внутри lambda, но не можем использовать оператор if (для него потребовалось бы несколько строк).

23.5.4) Когда использовать lambda, а когда именованные функции

Используйте lambda-выражения, когда:

  • Функция очень проста (одно выражение)
  • Функция используется только один раз или в очень локальном контексте
  • Определение именованной функции добавило бы ненужную многословность

Используйте именованную функцию, когда:

  • Функция сложная или требует нескольких операторов
  • Функция будет переиспользоваться в нескольких местах
  • Функции нужно описательное имя для ясности
  • Функции нужен docstring

23.5.5) Ограничения lambda и альтернативы

У lambda-выражений есть важные ограничения:

python
# ❌ Это не сработает — lambda не может содержать операторы
# bad_lambda = lambda x: 
#     if x > 0:
#         return x
#     else:
#         return -x
 
# ✅ Вместо этого используйте условное выражение
absolute_value = lambda x: x if x > 0 else -x
print(absolute_value(-5))  # Output: 5
print(absolute_value(3))   # Output: 3
 
# ✅ Для нескольких операций используйте обычную функцию
def process_and_double(x):
    print(f"Processing: {x}")
    return x * 2
 
result = process_and_double(5)  # Output: Processing: 5
print(result)                    # Output: 10

Lambda-выражения — это инструмент для конкретных ситуаций. Когда они делают код понятнее и лаконичнее — используйте их. Когда они делают код труднее для понимания — вместо этого используйте обычную именованную функцию.

23.6) Использование map() и filter() с простыми функциями

23.6.1) Функция map()

Функция map() применяет заданную function к каждому элементу итерируемого объекта (например, списка, кортежа или строки) и возвращает итератор с результатами. Это способ преобразовать каждый элемент коллекции без написания явного цикла.

python
map(function, iterable, *iterables)

Параметры:

  • function (обязательно): Функция, которая принимает один или несколько аргументов, обрабатывает их и возвращает значение. Функция вызывается один раз для каждого элемента в iterable(s).
  • iterable (обязательно): Последовательность (список, кортеж, строка и т. д.), элементы которой будут переданы в function.
  • *iterables (необязательно): Дополнительные итерируемые объекты для function, принимающей несколько аргументов.

Если передано несколько итерируемых объектов, функция должна принимать соответствующее число аргументов map() остановится, когда закончится самый короткий итерируемый объект

Возвращает:

Объект map (итератор), содержащий результаты, возвращённые function для каждого входного элемента.

Важно: объект map — это итератор, а не последовательность вроде list.

python
# Удвоим каждое число в списке
numbers = [1, 2, 3, 4, 5]
 
def double(x):
    return x * 2
 
# Применим double к каждому числу
doubled = map(double, numbers)
result = list(doubled)  # Преобразуем объект map (итератор) в список
print(result)  # Output: [2, 4, 6, 8, 10]

23.6.2) Использование map() с lambda

Lambda-выражения отлично подходят для map() при простых преобразованиях:

python
# Преобразуем температуру из Цельсия в Фаренгейт
celsius_temps = [0, 10, 20, 30, 40]
 
fahrenheit_temps = list(map(lambda c: (c * 9/5) + 32, celsius_temps))
print(fahrenheit_temps)  # Output: [32.0, 50.0, 68.0, 86.0, 104.0]

23.6.3) Функция filter()

Функция filter() применяет заданную function к каждому элементу iterable и возвращает итератор, содержащий только те элементы, для которых функция возвращает True. Это способ отбирать элементы из коллекции без написания явного цикла.

python
filter(function, iterable)

Параметры:

  • function: Функция, которая принимает один аргумент, проверяет его и возвращает True или False. Функция вызывается один раз для каждого элемента в iterable.
  • iterable: Последовательность (список, кортеж, строка и т. д.), элементы которой будут проверяться function.

Возвращает:

Объект filter (итератор), содержащий только те элементы, для которых function вернула True.

Важно: объект filter — это итератор, а не последовательность вроде списка.

Пример:

python
# Оставим только чётные числа
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
def is_even(x):
    return x % 2 == 0
 
# Применим is_even к каждому числу и оставим только те, для которых вернётся True
even_numbers = filter(is_even, numbers)
result = list(even_numbers)  # Преобразуем объект filter в список
print(result)  # Output: [2, 4, 6, 8, 10]

23.6.4) Использование filter() с lambda

Lambda-выражения часто используют с filter() для краткой фильтрации:

python
# Отфильтруем студентов, которые сдали (grade >= 60)
students = [
    {"name": "Alice", "grade": 85},
    {"name": "Bob", "grade": 55},
    {"name": "Charlie", "grade": 92},
    {"name": "Diana", "grade": 48},
    {"name": "Eve", "grade": 73}
]
 
passed = list(filter(lambda s: s["grade"] >= 60, students))
print("Students who passed:")
for student in passed:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Alice: 85
#   Charlie: 92
#   Eve: 73

23.6.5) Комбинирование map() и filter()

Вы можете объединять операции map() и filter(), чтобы выполнять более сложные преобразования:

python
# Получим квадраты чётных чисел
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
# Сначала отфильтруем чётные числа, затем возведём их в квадрат
even_numbers = filter(lambda x: x % 2 == 0, numbers)
squared = map(lambda x: x ** 2, even_numbers)
result = list(squared)
print(result)  # Output: [4, 16, 36, 64, 100]

Визуальное сравнение: map() vs filter()

filter() — оставляет НЕКОТОРЫЕ элементы

Вход: [1, 2, 3, 4, 5]

Проверка: is_even(x)

Выход: [2, 4](равная или меньшая длина)

map() — преобразует ВСЕ элементы

Вход: [1, 2, 3, 4, 5]

Применить: double(x) = x * 2

Выход: [2, 4, 6, 8, 10](та же длина)

Ключевые различия:

  • map(): Применяет функцию, чтобы преобразовать каждый элемент → выход имеет ту же длину
  • filter(): Проверяет каждый элемент и оставляет только прошедшие проверку → выход имеет равную или меньшую длину

В этой главе мы рассмотрели мощные возможности Python для функционального программирования. Мы узнали, что функции являются объектами первого класса и могут передаваться так же, как любое другое значение, что позволяет создавать гибкие и переиспользуемые шаблоны кода. Мы увидели, как функции могут возвращать другие функции, создавая замыкания, которые помнят своё окружение. Мы изучили lambda-выражения для кратких определений функций и использовали map() и filter() для элегантной обработки коллекций.

Эти концепции формируют основу для продвинутых техник программирования на Python. В главе 38 мы развим эти знания, чтобы освоить декораторы, одну из самых элегантных возможностей Python.


© 2025. Primesoft Co., Ltd.
support@primesoft.ai