虽然我不太喜欢 python,不过它已经变成 lingua franca 了,语法也还行不是特别丑…… 所以用它举例最容易被人理解。
闭包
闭包的定义是(个人给出):一个捕获了其环境中变量的函数。
下面列出一个例子,说明闭包的实现。
可以看到,虽然 g1
和 g2
是不同的闭包,但它们作为函数的 entry address 都是一样的,区别就是捕获的环境不一样。
##############################################################################
# CLOSURE
def f(a):
x=a
# `g` is what we call a "closure"
def g(y):
z=3
return x+y+z # uses f's local var `x`
return g
# the returned `g` uses f's local var `x`, but `x` as a local var no longer exists.
# thus we need to 'capture' `x`, copy it to somewhere more persistent than a stackframe.
# the code above is functionally equivalent to
def gg(env, y):
z = 3
return env['x'] +y + z
def ff(a):
x=a
env = {'x': x}
return (env, gg)
# therefore at runtime when we call
g1 = f(2)
g1(3) # 8
g2 = f(6)
g2(1) # 10
# it's the same as
env1, g = ff(2)
g(env1, 3)
env2, g = ff(6)
g(env2, 1)
generator
generator 也很类似闭包,它还能用来实现 async / await。
一个有 yield
的函数就是一个 generator,其返回值可以被 next
(如下)。
generator 本质上是一种 coroutine,能够有多个入口(每个 yield 之后都是一个入口),多个出口(每个 yield 都是一个出口)。
编译器内部会把 generator 编译成一个状态机,如 f
有三个状态。
然后每次调用 next
就是状态机走一步。
所以这里其实有一个问题就是,状态机的大小。 一般来说所有局部变量都会在状态机里面,但如果局部变量比较多,或者 generator 里面又有函数调用因此 callee 的局部变量也要在状态机里面(虽然对 python 这一点不成立——见最后的例子),那状态机的空间会暴涨。 这里和这里描述了一种优化方法,基本意思就是不会同时 live 的变量可以放到一个 union(指 C 的 union)里面节省空间。
##############################################################################
# GENERATOR
# define a generator.
def f(n):
# init
i=0
while i < n:
print('f ',i)
yield i # loop
i+=1
# end
# use the generator
gen=f(2)
next(gen) # f 0
# 0
next(gen) # f 1
# 1
next(gen) # raise StopIteration
# usually control enters a function in at its entry and leaves at a 'return',
# but control can enter 'f' right after the 'yield' statement, and leave 'f' at the 'yield'
# i.e. control flow is more flexible with 'f' than usual functions
# implementation of generator:
# GENERATORS ARE COMPILED TO STATE MACHINES, possibly like
def ff_g(st):
while True:
if st['kind'] == 'init':
st['i'] = 0
if st['i'] < st['n']:
print('f ', st['i'])
st['kind'] = 'loop'
return st['i']
else:
st['kind'] = 'end'
continue
if st['kind'] == 'loop':
st['i'] += 1
if st['i'] < st['n']:
print('f ', st['i'])
st['kind'] = 'loop'
return st['i']
else:
st['kind'] = 'end'
continue
if st['kind'] == 'end':
raise StopIteration()
def ff(n):
state = {'kind': 'init', 'n': n}
return (state, ff_g)
# when used
st, gen = ff(2) # each generator is bound with an implicit state
st # {'kind': 'init', 'n': 2}
gen(st) # i.e. next(gen)
# f 0
# 0
gen(st) # f 1
# 1
gen(st) # raise StopIteration
st # {'i': 2, 'kind': 'end', 'n': 2}
yield 还有一个语法 yield from
def yf():
yield from [1, 2, 3]
# 等价于
def y():
for i in [1, 2, 3]:
yield i
如果有嵌套 yield,python 只会返回最外层的。原因请读者结合上面的例子,自行揣摩。
def f():
yield from [1, 2, 3]
def g():
f()
yield 4
print(list(g())) # [4]