Функции – полноправные объекты в Python

Раздел: Python -> Встроенные возможности

Функция как объект в Python

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

def square(x):
    return x * x

# Присвоение переменной
f = square
print(f(5))  # Вывод: 25

# Проверка типа
print(type(f))  # <class 'function'>

объект функция python (функция как объект в python)

25
<class 'function'>

Переменная f ссылается на тот же объект, что и square. Имя функции не имеет значения, важна сама ссылка.

Как присвоить функцию переменной и вызвать её?

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

def add(a, b):
    return a + b

operation = add          # ссылка на функцию
result = operation(3, 4) # вызов через переменную
print(result)            # 7
7

Распространённая ошибка: написать operation = add() – это вызовет функцию и присвоит результат, а не саму функцию. При попытке вызвать operation(3,4) возникнет ошибка, если add() не вернула вызываемый объект.

Как передать функцию в качестве аргумента другой функции?

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

def apply_twice(func, value):
    return func(func(value))

def increment(x):
    return x + 1

print(apply_twice(increment, 5))  # 7

# Встроенная функция sorted с ключом
words = ['яблоко', 'груша', 'ананас']
sorted_words = sorted(words, key=len)
print(sorted_words)  # ['груша', 'яблоко', 'ананас']
7
['груша', 'яблоко', 'ананас']

Частая путаница: передача функции без скобок (ссылку) и со скобками (результат). Если нужно применить функцию позже, передаётся ссылка. Если передать результат, то в apply_twice попадёт число, а не функция.

Как вернуть функцию из функции (замыкание)?

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

def multiplier(n):
    def multiply(x):
        return x * n
    return multiply

double = multiplier(2)
triple = multiplier(3)

print(double(10))  # 20
print(triple(10))  # 30
20
30

Проблема позднего связывания: если в цикле создавать замыкания, захватывающие переменную цикла, все замыкания будут видеть последнее значение. Решение – использовать аргумент по умолчанию или functools.partial.

Как использовать lambda для создания анонимной функции?

Лямбда-выражение создаёт объект функции без имени. Удобно для коротких преобразований, особенно внутри вызовов map, filter, sorted.

# Традиционная функция
def square(x):
    return x * x

# Лямбда-аналог
square_lambda = lambda x: x * x

print(square_lambda(4))  # 16

# Применение с filter
numbers = [1, 2, 3, 4, 5]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)  # [2, 4]
16
[2, 4]

Ограничения: лямбда может содержать только одно выражение, нельзя использовать операторы (if, for, try). Для сложной логики лучше определить обычную функцию.

Как хранить функции в списке или словаре?

Поскольку функции – объекты, их можно поместить в коллекции. Это позволяет выбирать действие по индексу или ключу.

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

operations = [add, subtract, multiply]
print(operations[1](10, 3))  # 7

# Словарь с ключами-строками
ops_dict = {
    '+': add,
    '-': subtract,
    '*': multiply
}
print(ops_dict['*'](4, 5))  # 20
7
20

Ошибка при обращении: если ключа нет в словаре, возникнет KeyError. Следует предусмотреть словарь с методом get или обработчик исключения.

Как добавить атрибуты функции?

Функции – объекты, поэтому им можно динамически добавлять произвольные атрибуты. Это удобно для хранения метаданных или состояния (например, счётчик вызовов).

def count_calls():
    count_calls.counter += 1
    return count_calls.counter

count_calls.counter = 0  # инициализация атрибута

print(count_calls())  # 1
print(count_calls())  # 2
print(count_calls.counter)  # 2
1
2
2

Предостережение: атрибуты не являются частью сигнатуры функции, легко опечататься в имени. Для подсчёта вызовов лучше использовать замыкание или декоратор со счётчиком.

Расширенные примеры использования функций как объектов

Ниже приведены более сложные сценарии, показывающие гибкость функций в Python.

Декоратор для логирования вызовов

Пример
def logger(func):
    def wrapper(*args, **kwargs):
        print(f"Вызов {func.__name__} с args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"Результат: {result}")
        return result
    return wrapper

@logger
def add(a, b):
    return a + b

add(3, 5)
Вызов add с args=(3, 5), kwargs={}
Результат: 8

Декоратор logger принимает функцию и возвращает новую, обёрнутую в логирование. Синтаксис @logger эквивалентен add = logger(add).

Мемоизация (кэширование результатов)

Пример
from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))  # 55
print(fibonacci.cache_info())  # статистика кэша
55
CacheInfo(hits=18, misses=11, maxsize=None, currsize=11)

Декоратор lru_cache сохраняет результаты вызовов – это пример функции высшего порядка, возвращающей модифицированную функцию.

Композиция функций

Пример
def compose(f, g):
    """Возвращает h(x) = f(g(x))"""
    def h(x):
        return f(g(x))
    return h

def double(x):
    return x * 2

def increment(x):
    return x + 1

double_after_increment = compose(double, increment)
print(double_after_increment(5))  # double(increment(5)) = 2*(5+1) = 12
12

Композиция объединяет две функции в одну. Это реализация паттерна проектирования.

Частичное применение (currying) с functools.partial

Пример
from functools import partial

def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(square(5))  # 25
print(cube(5))    # 125
25
125

partial создаёт новую функцию, где часть аргументов фиксирована. Возвращаемый объект является callable.

Создание функции с помощью type (динамическое определение)

Пример
# Использование exec для создания функции (осторожно)
code = """
def dynamic_func(x):
    return x ** 2 + 1
"""
local_dict = {}
exec(code, globals(), local_dict)
dynamic_func = local_dict['dynamic_func']
print(dynamic_func(3))  # 10
10

Подход с exec и type позволяет создавать функции на лету из строки, но его применяют редко из-за рисков безопасности и сложности отладки.

Функция как объект в Python - comments

En
объект функция python (python)