PythonJuniorTechnical
Что такое first-class functions в Python? Что это даёт на практике?
Функции в Python — объекты первого класса: их можно присваивать переменным, передавать аргументами, возвращать из функций и хранить в коллекциях. У них есть атрибуты (__name__, __doc__, __annotations__). Базис для callback-ов, декораторов, dependency injection.
Что это значит
«First-class» означает, что функции — обычные объекты типа function (или builtin_function_or_method). С ними можно делать всё, что с любым другим объектом:
- Присваивать переменным:
f = process— ссылка, не вызов. - Передавать аргументами:
sorted(items, key=str.lower). - Возвращать из других функций (closures, decorators, фабрики).
- Хранить в коллекциях:
handlers = {"GET": handle_get, "POST": handle_post}. - Сравнивать по identity, передавать через очередь, сериализовать (через
pickle— с ограничениями).
Каждая функция имеет атрибуты: __name__, __qualname__, __module__, __doc__, __annotations__, __code__, __defaults__, __closure__, __dict__. На них можно вешать кастомные атрибуты (используют декораторы).
Практические применения
- Higher-order functions:
map,filter,sorted(..., key=...),functools.reduce. - Декораторы: функция, принимающая функцию и возвращающая обёртку.
- Callbacks: signal handlers, GUI events, ARQ/Celery tasks.
- Strategy pattern: словарь имени → реализация вместо if/elif лестницы.
- Dependency injection: передавайте функции вместо классов, когда не нужно состояние.
- Partial application:
functools.partial(fn, x=1)создаёт новую callable с зафиксированными аргументами.
Пример
from functools import partial, reduce
def by_length(value: str) -> int:
return len(value)
def apply(func, value):
return func(value)
names = ["Ada", "Guido", "Linus"]
print(sorted(names, key=by_length)) # ['Ada', 'Guido', 'Linus']
print(apply(str.upper, "python")) # PYTHON
# Атрибуты функции
print(by_length.__name__) # by_length
print(by_length.__annotations__) # {'value': <class 'str'>, 'return': <class 'int'>}
by_length.cache_hits = 0 # повесить свой атрибут
print(by_length.cache_hits)
# Словарь команд — strategy pattern на функциях
def cmd_ping(args): return "pong"
def cmd_echo(args): return " ".join(args)
COMMANDS = {"ping": cmd_ping, "echo": cmd_echo}
print(COMMANDS["echo"](["hello", "world"])) # hello world
# partial — фиксируем аргумент
log_error = partial(print, "[ERROR]")
log_error("disk full") # [ERROR] disk full
# reduce — высокоуровневая агрегация
print(reduce(lambda a, b: a * b, [1, 2, 3, 4])) # 24
# Декоратор как пример возврата функции
def trace(fn):
def wrapper(*args, **kwargs):
print(f"call {fn.__name__}({args}, {kwargs})")
return fn(*args, **kwargs)
return wrapper
@trace
def add(a, b): return a + b
add(1, 2)
Подводные камни
- Путать
fn(ссылка) иfn()(вызов) — передаёте результат вместо функции и удивляетесь падению. - Method bound vs unbound:
obj.method— bound (содержитself);Class.method— unbound (нужно явно передатьself). - Декоратор без
functools.wrapsтеряет__name__/__doc__исходной функции; ломает интроспекцию и pytest. - Lambda удобна на месте, но именованная функция лучше для тестов и stack traces.
- Сохранение функции в БД/файл — пиклить можно только top-level функции; lambda/closure не сериализуются.
- Mutable default argument — частая ловушка:
def f(x, lst=[]):делитlstмежду вызовами.
Common mistakes
- Говорить только про lambda, не объясняя объект функции.
- Не приводить практические сценарии.
- Путать ссылку на функцию и вызов.
What the interviewer is testing
- Передаёт функцию как аргумент.
- Объясняет callbacks/decorators.
- Понимает, что функции имеют атрибуты и identity.