前言

全部以 Python 为例说明.

一等函数

满足如下条件一般可以称为一等对象:

  • 运行时创建
  • 能赋值给变量
  • 能作为参数传递给函数
  • 能作为函数的返回结果

Python 中函数就是一等对象, 也可以叫做一等函数:

# 赋值给变量
def foo():
    return "foo"
var = foo
var() # --> foo

# 作为参数传递给函数
def accept_func(func):
    return func()
accept_func(foo) # --> foo

# 作为函数返回值
def outter():
    def inner():
        return "inner"
    return inner
var = outter()
var() # --> inner

具有一等函数的编程语言都会涉及到如何处理自由变量(free variable)的问题, 同时会引出闭包的概念. 而且可以发现, 装饰器其实就是一种闭包.

闭包

一个函数引用了一个自由变量, 这个函数和自由变量一同存在, 即使离开了自由变量的创建环境外部变量依然不会消失的时候, 可以称之为闭包.

套用一句调侃的总结: 发哥是自带背景音乐的男人, 闭包是自带运行环境的函数.

所以闭包应该是包含两部分:

  1. 函数主体
  2. 函数的运行环境, 其实就是那个所谓的自由变量

一般来说可以产生闭包的条件都是一个外部函数里定义了一个变量和一个内部函数, 而这个内部函数又使用到了这个变量:

def outter():
    var = 1 # 自由变量
    def inner():
        return var
    return inner
foo = outter() # 执行完这句后, foo 就是 inner 了, 而且 var 并未随着 outter 一起消失
print foo()    # 将打印出 var 的值, 1

闭包其实是实现了一等函数的编程语言们不得不面对的一个问题: 既然你的函数是一等对象, 可以返回作为函数返回值, 那么如果你返回的这个函数引用了自由变量的话, 这些自由变量怎么处理呢, 丢弃掉还是跟随返回的函数一同存在呢?

大多数编程语言选择了自由变量跟随返回的函数一同存在, 这就成为了闭包, 只要函数存在, 自由变量就会一同存在.

闭包的实现:

不是说函数内部的变量都在栈上, 函数退出后不是应该随着栈清空而消失吗? 其实很简单, 既然变量不会消失, 那么实现闭包的变量就没有在栈上, 而是存在于堆上, 由垃圾自动回收机制来管理. 所以实现闭包的语言大多都是支持垃圾回收机制的语言, 因为他们实现闭包比较容易.

语法糖

首先需要明确的是, Python 中的装饰器符号 @ 只是一个语法糖, 其效果如下:

def deco(func):
    return func

@deco
def foo():
    pass

等效于:

def deco(func):
    return func

def foo():
    pass

foo = deco(foo)

这里只需要切记一点, 装饰器符号 @ 等效于下列写法:

foo = deco(foo)

foo 传递给 deco(), 在 deco() 中完成装饰后, 重新赋值给 foo, 此时 foo 就成为了被装饰后的新函数了.

装饰器

装饰器的目的是利用一个高阶函数(可以返回函数的函数)返回一个函数, 用这个返回函数去替换原有的函数. 我们返回的这个函数会调用原有的函数, 而且会加上自己的操作, 起到装饰的效果.

而且需要注意的是, 因为我们返回的函数中(一般写作 wrapper), 引用了传入的原有函数(一般是写作 func), 此处 func 就是自由变量, wrapper 是函数主体, 所以返回的这个 wrapper 满足闭包的条件, 所以是一个闭包.

不带参数的装饰器:

def deco(func): # 这里装饰器, 是一个高阶函数, 因为它返回 wrapper
    def wrapper(): # 如果不定义这个函数, deco() 本身是函数调用, 此处的代码会立即执行
        print "message from wrapper"
        return func()
    return wrapper

def foo():
    print "message from foo"

foo = deco(foo)
foo()

# 如果使用语法糖, 仅需如下方式即可
@deco # 注意这里没有圆括号
def foo():
    print "message from foo"
foo()

注意一下, 因为使用装饰器其实是进行了函数调用, 所以我们需要在内部定义 wrapper 函数, 并将其返回, 否则会成为立即调用的效果. 而事实上我们需要的是装饰器返回一个函数, 用这个函数去替换原有的函数.

带参数的装饰器:

def deco_factory(arg=1): # 这里是装饰器工厂, 根据参数不同返回不同的装饰器
    def deco1(func): # 装饰器1
        def wrapper(): # 装饰器内部如往常一样, 需要定义一个函数, 否则会立即执行
            print "message from deco1"
            return func()
    def deco2(func): #装饰器2
        def wrapper():
            print "message from deco2"
            return func()
        
    if arg == 1:
        return deco1
    else:
        return deco2

@deco_factory() # 注意这里的圆括号, 此处已经是函数调用了. 注意对比不带参数的写法
def foo():
    print "message from foo"

不带参数和带参数的装饰器的一般定义法:

# 不带参数, 自己就是装饰器, 返回一个内部函数即可
def deco(func):
    def wrapper():
        return func()
    return wrapper

# 使用时不要带括 => foo = deco(foo), foo 其实已经成了 wrapper
@deco
def foo():
    pass

# 带参数, 自己是装饰器工厂, 返回的是装饰器, 所以看起来会比上述写法还有多一层函数定义
def deco_factory(arg=None):
    def deco(func):
        def wrapper():
            return func()
        return wrapper
    return deco

# 使用时需要带括号 => foo = deco_factory()(foo), 注意 deco_factory() 返回 deco
@deco_factory()
def foo():
    pass
Comments
Write a Comment