虽然我不太喜欢 python,不过它已经变成 lingua franca 了,语法也还行不是特别丑…… 所以用它举例最容易被人理解。

闭包

闭包的定义是(个人给出):一个捕获了其环境中变量的函数。

下面列出一个例子,说明闭包的实现。 可以看到,虽然 g1g2 是不同的闭包,但它们作为函数的 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]