1. 保存有状态的过程

在scheme中使用conscarcdr构造抽象数据,但cons构造的抽象数据却是一个过程:

(define (cons x y)
  (define (dispatch m)
    (cond ((= m 0) x)
          ((= m 1) y)
          (else (error "argument not 0 or 1 -- cons" m))))
  dispatch)

(define (car z) (z 0))

(define (cdr z) (z 1))

>> (define z cons(1 2))
>> (car z)
   1
>> (cdr z)
   2
>> (define l cons(z 3))
>> (car (car l))
   1

解释一下:

  • 变量作用域: local > enclosing > global > build-in

    cons函数中,x, y未在函数内部(local),外部(global)定义,也不是解释器内置的变量(build-in),x, y的作用域即为enclosing。

  • closure: 内部函数中对enclosing作用域的变量进行引用

    cons函数中,定义函数dispatchdispatch引用了x, y,并且作为cons的返回值,当cons执行完成之后,虽然会对内部变量回收,但dispatch作为返回值,保留了x, y的引用。

    cardispatch的函数类型为参数时,内部调用(dispatch 0),并返回其执行结果,即为(cons x y)中的x,同理(cdr dispatch)返回y

由以上可知,过程(函数)不仅仅只是一系列动作的集合,它还可以保存有自己的状态(变量)。当我们使用cons(1, 2)时,实际上得到了一个保持有自己变量1、2的过程。

2. 闭包

闭包即一个函数,通过它可以引用由包含由这个函数的代码所定义的变量,一个简单的例子(来自sicp)(这里采用了消息传递的方法):

(define (make-account balance)
  (define (withdraw amount)
    (if (>= balance amount)
        (begin (set! balance (- balance amount))
               balance)
        "Insufficient funds"))
  (define (deposit amount)
    (set! balance (+ balance amount))
    balance)
  (define (dispath m)
    (cond ((eq? m 'withdraw) withdraw)
          ((eq? m 'deposit) deposit)
          (else (error "Unknown request -- MAKE-ACCOUNT"
                       m))))
  dispatch)

>> (define acc (make-account 100))
>> ((acc 'withdraw) 50)
   50
>> ((acc 'deposit) 60)
   110
>> ((acc 'withdraw) 120)
   "Insufficient funds"

所以,闭包就是一个函数被当作值返回时,相当于返回了一个通道,这个通道可以访问这个函数enclosing作用域中的变量,即函数所需要的数据结构保存了下来,数据结构中的值在外层函数执行时创建,外层函数执行完毕时理应销毁,但由于内部函数作为值返回出去,这些值得以保存下来。而且无法直接访问,必须通过返回的函数,这也就是私有性。

显然,闭包的形成很简单,在执行过程完毕后,返回函数,或者将函数得以保留下来,即形成闭包。

可以查看scip中对环境模型的解释来理解闭包

3. Python的闭包

Python支持闭包(但python依然区分语句和表达式,所以明确指定return语句返回函数)。

def cons(x, y):
    def dispatch(m):
        if m == 0:
            return x
        elif m == 1:
            return y
        else:
            print("Argument not 0 or 1 -- CONS",m)
    return dispatch

def car(z):
    return z(0)

def cdr(z):
    return z(1)

还有一点,有人用以下例子说明Python不能正确实现闭包:

funcs = [(lambda n: n + i) for i in range(10)]

print(funcs[3](5))

输出14,但这是因为存放的是i的引用,而非i的值。i的最终值为9,则funcs中所有的lambda引用i的值都为9。

这只是Python传引用的坑而已,并不能说明Python不能正确实现闭包,因为每个函数是可以存储变量的,比如:

funcs = [(lambda n, i=i: n + i) for i in range(10)]

print(funcs[3](5))

这次输出为8,因为使用局部变量拷贝每个外部变量i后,lambda可以存放不同的局部变量i。

4. Python装饰器

  • 装饰器用来装饰函数
  • 返回一个函数对象
  • 被装饰函数标识符指向返回的函数对象
  • 语法糖 @deco
import functools

def log1(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

def log2(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator
import time
from functools import wraps

def logger(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        ts = time.time()
        result = fn(*args, **kwargs)
        te = time.time()
        print("function = {0}".format(fn.__name__))
        print("arguments = {0}{1}".format(args, kwargs))
        print("return = {0}".format(result))
        print("time = %.6f sec" % (te - ts))
        return result
    return wrapper

@logger
def multipy(x, y): # multipy = logger(multipy)
    return x*y

@logger
def sum_num(n): # sum_num = logger(sum_num)
    s = 0
    for i in range(n+1):
        s +=i
    return s

print(multipy(2, 10))
print(sum_num(100))
from functools import wraps

def memo(fn):
    cache = {}
    miss = object()

    @wraps(fn)
    def wrapper(*args):
        result = cache.get(args, miss)
        if result is miss:
            result = fn(*args)
            cache[args] = result
        return result

    return wrapper

@memo
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

javascript

var fibonacci = function() {
    var a = 1;
    var b = 1;
    return function() {
        var temp = b;
        b = a+b;
        a = temp;
        return b;
    }
}

var fibInstance = fibonacci();