Функции – полноправные объекты в 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) # 77
Распространённая ошибка: написать 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)) # 3020 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)) # 207 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) # 21 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) = 1212
Композиция объединяет две функции в одну. Это реализация паттерна проектирования.
Частичное применение (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)) # 12525 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)) # 1010
Подход с exec и type позволяет создавать функции на лету из строки, но его применяют редко из-за рисков безопасности и сложности отладки.