0%

Python 闭包

在 Python 中,闭包通常是定义在另一个函数内部的函数。这个内部函数抓取在其外层作用域中定义的对象,并将它们与内部函数对象本身关联起来。由此产生的组合称为闭包。在Python中,闭包可以很轻松的实现。

close closure 和 inner function

在Python中,所有的闭包都是内部函数,inner function,但不是所有的inner function都是闭包,闭包要求返回内部函数的一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
# 这是闭包
def outer_function():
x = 10
def inner_function():
print(x)
return inner_function

# 这不是闭包
def outer_function():
x = 10
def inner_function():
print(x)

作用域

inner function可以访问的变量作用域很广泛,除了简单的全局变量和inner function的局部变量,还可以访问outter function的变量,包括更新。即使outer function已经返回,inner function仍然可以访问outer function的变量。这和C++中的变量捕获范围非常不同。

1
2
3
4
5
6
7
8
>>> def outer_func():
... name = "Pythonista"
... return lambda: print(f"Hello, {name}!")
...

>>> greeter = outer_func()
>>> greeter()
Hello, Pythonista!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> def outer_func(outer_arg):
... local_var = "Outer local variable"
... def closure():
... print(outer_arg)
... print(local_var)
... print(another_local_var)
... another_local_var = "Another outer local variable"
... return closure
...

>>> closure = outer_func("Outer argument")

>>> closure()
Outer argument
Outer local variable
Another outer local variable

可变对象和不可变对象

当闭包修改不可变对象时,需要使用nonlocal关键字。

1
2
3
4
5
6
7
>>> def outer_func():
... x = 10
... def inner_func():
... nonlocal x
... x += 1
... print(x)
... return inner_func

可变变量则可以直接修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> def make_appender():
... items = []
... def appender(new_item):
... items.append(new_item)
... return items
... return appender
...

>>> appender = make_appender()

>>> appender("First item")
['First item']
>>> appender("Second item")
['First item', 'Second item']
>>> appender("Third item")
['First item', 'Second item', 'Third item']

闭包的使用场景

保存状态/状态函数

闭包可以保存状态,例如计数器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> def counter():
... count = 0
... def increment():
... nonlocal count
... count += 1
... return count
... return increment
...

>>> increment = counter()
>>> increment()
1
>>> increment()
2
>>> increment()
3
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> def make_root_calculator(root_degree, precision=2):
... def root_calculator(number):
... return round(pow(number, 1 / root_degree), precision)
... return root_calculator
...

>>> square_root = make_root_calculator(2, 4)
>>> square_root(42)
6.4807

>>> cubic_root = make_root_calculator(3)
>>> cubic_root(42)
3.48
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> def cumulative_average():
... data = []
... def average(value):
... data.append(value)
... return sum(data) / len(data)
... return average
...

>>> stream_average = cumulative_average()

>>> stream_average(12)
12.0
>>> stream_average(13)
12.5
>>> stream_average(11)
12.0
>>> stream_average(10)
11.5

回调函数

1
2
3
4
5
6
7
8
9
10
11
>>> def on_click(callback):
... def handle_click(event):
... callback(event)
... return handle_click
...

>>> def handle_click(event):
... print(f"Clicked at {event.x}, {event.y}")
...

>>> on_click(handle_click)

装饰器

1
2
3
4
5
6
7
>>> def decorator(function):
... def closure():
... print("Doing something before calling the function.")
... function()
... print("Doing something after calling the function.")
... return closure
...
1
2
3
4
5
6
7
8
9
>>> @decorator
... def greet():
... print("Hi, Pythonista!")
...

>>> greet()
Doing something before calling the function.
Hi, Pythonista!
Doing something after calling the function.

工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> def make_root_calculator(root_degree, precision=2):
... def root_calculator(number):
... return round(pow(number, 1 / root_degree), precision)
... return root_calculator
...

>>> square_root = make_root_calculator(2, 4)
>>> square_root(42)
6.4807

>>> cubic_root = make_root_calculator(3)
>>> cubic_root(42)
3.48

缓存/记忆

尽管可以使用闭包实现缓存/记忆,但Python有更高效的方式。Python 标准库中内置了 memoization。如果您需要在项目中使用缓存,可以使用 functools 模块中的 @cache@lru_cache

1
2
3
4
5
6
7
8
>>> def memoize(function):
... cache = {}
... def closure(number):
... if number not in cache:
... cache[number] = function(number)
... return cache[number]
... return closure
...
1
2
3
4
from time import sleep

def slow_operation(number):
sleep(0.5)
1
2
3
4
5
6
7
8
>>> from timeit import timeit

>>> timeit(
... "[slow_operation(number) for number in [2, 3, 4, 2, 3, 4]]",
... globals=globals(),
... number=1,
... )
3.02610950000053
1
2
3
4
5
6
7
8
9
10
11
>>> @memoize
... def slow_operation(number):
... sleep(0.5)
...

>>> timeit(
... "[slow_operation(number) for number in [2, 3, 4, 2, 3, 4]]",
... globals=globals(),
... number=1,
... )
1.5151869590008573

面向对象的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def Stack():
_items = []

def push(item):
_items.append(item)

def pop():
return _items.pop()

def closure():
pass

closure.push = push
closure.pop = pop
return closure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> from stack_v2 import Stack

>>> stack = Stack()
>>> stack.push(1)
>>> stack.push(2)
>>> stack.push(3)

>>> stack.pop()
3

>>> stack._items
Traceback (most recent call last):
...
AttributeError: 'function' object has no attribute '_items'