Why Are Closures Called Closures? (Python)
itsmehao
0:00 / 0:00
Why Are Closures Called Closures? (Python)
43 просмотра · 11 дн. назад
itsmehao
10 подписчиков
43 просмотра · 11 дн. назад
Why are closures called closures? You probably know that an inner function can keep a variable of its outer function alive, even after the outer function has returned. But keeping something alive doesn't sound like closing anything, and how long does that variable actually stay alive? This video follows one tiny greeter function to the real model: gaps (free variables), cells, and what it means for a function to be closed. Then the two ways closures get used, escaping and non-escaping, the everyday Python code built on them, and the most famous closure bug.
Chapters
0:00 The usual definition, and a puzzle
1:30 A function with a gap
2:25 Closing the gap: what Python really does
4:14 Why it's called a closure
5:29 Two ways to use a closure: non-escaping and escaping
6:43 Non-escaping: a sorting key
7:38 Escaping: a callback
9:32 The classic closure bug
10:34 Back to the puzzle
More details
Global variables aren't captured: a top-level function that uses a module-level variable has no cells (__closure__ is None); Python looks the global up each time it runs.
Several closures made in the same call share a cell for the same variable; a change through one is seen by the other.
Since Python 3.12 (PEP 709), list/dict/set comprehensions are inlined and are no longer closures; generator expressions still are.
filter and map are lazy iterators that hold on to their lambda: wrap them in list(...) to use the lambda up inside the call; returning the bare filter object lets the lambda escape.
"Freed when nothing refers to it" is CPython reference counting; objects in reference cycles wait for the cycle collector, and other implementations such as PyPy free memory at different times.
A stored callback keeps everything in its cells alive (the whole cart) as long as it is stored. Forgotten callbacks are a common memory leak.
Cells can be read, and even written, through __closure__[i].cell_contents.
Other escaping patterns not shown: function factories (functools.partial is a ready-made alternative), private state with nonlocal, decorators such as a memoizer (functools.cache).
Swift: closure parameters are non-escaping by default since Swift 3 and need @escaping to be stored. Rust has no "escaping" keyword; it expresses the same idea with lifetimes, 'static bounds and move closures.
History: Peter Landin, "The Mechanical Evaluation of Expressions" (1964); Scheme popularised lexical closures in the 1970s.
Every snippet was run on CPython 3.11, 3.12, 3.13, 3.14 and 3.14t (free-threaded). Memory addresses in the _closure_ output differ per run.