Замыкания и lambda

⏱ 25 мин · 🧩 заданий: 5

Вложенные функции

Функцию можно объявить внутри другой функции. Внутренняя видит переменные внешней:

def greet(name):
    def make_message():
        return f"Привет, {name}!"     # name из объемлющей функции
    return make_message()

print(greet("Аня"))

Замыкание

Если внешняя функция возвращает внутреннюю, та продолжает «помнить» переменные внешней, даже когда внешняя уже завершилась:

def make_power(exp):
    def power(base):
        return base ** exp
    return power

square = make_power(2)
cube = make_power(3)
print(square(5), cube(2))
print(square.__closure__, square.__closure__[0].cell_contents)
print(square.__code__.co_freevars)

Нажмите 👣 Пошагово: make_power завершается, её кадр исчезает, но функция power хранит ссылку на exp.

Замыкание = функция + ссылки на переменные объемлющей области (они хранятся в ячейках, __closure__). Такие переменные называются свободными (co_freevars).

square ──▶ функция power ├── __code__: return base ** exp └── __closure__: (ячейка ──▶ 2) cube ──▶ функция power └── __closure__: (ячейка ──▶ 3)

Зачем замыкания

  • Фабрики функций: настроить функцию заранее (make_power(2)).
  • Состояние без класса: счётчики, кеши, аккумуляторы (с nonlocal).
  • Декораторы — целиком построены на замыканиях (следующие уроки).
  • Колбэки с контекстом: button.on_click(make_handler(user_id)).
def make_counter(start=0):
    count = start
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

clicks = make_counter()
views = make_counter(100)
print(clicks(), clicks(), views(), clicks())

Позднее связывание — ловушка

Замыкание хранит ссылку на переменную, а не на её значение. Значение читается в момент вызова:

funcs = []
for i in range(3):
    funcs.append(lambda: i)
print([f() for f in funcs])       # [2, 2, 2] — к моменту вызова i == 2

# Исправление 1: значение по умолчанию вычисляется при создании функции
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])

# Исправление 2: functools.partial
from functools import partial
def show(i):
    return i
funcs = [partial(show, i) for i in range(3)]
print([f() for f in funcs])

На собеседовании

«Что выведет [f() for f in [lambda: i for i in range(3)]]?» — [2, 2, 2]. Объясните: замыкание захватывает переменную, а не значение; поиск значения происходит при вызове. Исправление — lambda i=i: i или partial.

lambda

lambda параметры: выражение создаёт функцию из одного выражения:

add = lambda a, b: a + b
print(add(2, 3), add.__name__)
print(sorted(["бб", "а", "ввв"], key=lambda s: len(s)))
print(list(map(lambda x: x ** 2, [1, 2, 3])))
print((lambda x: "чёт" if x % 2 == 0 else "нечет")(7))

Ограничения: одно выражение — нет присваиваний (кроме :=), return, циклов, try; нет docstring; в трассировке ошибок имя <lambda>.

Когда lambda уместна

Как короткий аргумент: key=, map, filter, колбэки. Если функции нужно имя — пишите def. PEP 8 прямо не рекомендует f = lambda x: ....

Итог

  • Вложенная функция видит переменные объемлющей; если её вернуть — получится замыкание.
  • Замыкание хранит ссылки на переменные в ячейках __closure__; изменять их — через nonlocal.
  • Позднее связывание: значение читается при вызове; исправление — x=x или partial.
  • lambda — функция из одного выражения для коротких аргументов.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Программа на Python

Напишите фабрику make_multiplier(n), которая возвращает функцию, умножающую аргумент на n. double = make_multiplier(2); double(21) → 42.

Какие тесты проверяют код (3)
assert make_multiplier(2)(21) == 42
triple = make_multiplier(3); assert [triple(x) for x in range(4)] == [0, 3, 6, 9]
assert make_multiplier(2).__closure__ is not None, "Функция должна быть замыканием"
Задание 2 Один вариант ответа

Что выведет код?

funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])
Задание 3 Программа на Python

Исправьте функцию make_adders(): она должна вернуть список из трёх функций, где k-я функция прибавляет k к аргументу (adders[0](10) == 10, adders[2](10) == 12).

Какие тесты проверяют код (1)
a = make_adders(); assert [f(10) for f in a] == [10, 11, 12]
Задание 4 Программа на Python

Напишите функцию make_averager(), которая возвращает функцию avg(x): каждый вызов добавляет число к истории и возвращает среднее всех переданных чисел.

Какие тесты проверяют код (1)
a = make_averager(); assert [a(10), a(20), a(30)] == [10.0, 15.0, 20.0]
Задание 5 Несколько вариантов

Что верно про lambda?

Войдите, чтобы отмечать пройденные уроки.