Python 闭包

Python 闭包

闭包(closure)是函数式编程中重要的语法结构,可以提高了代码的可重用性(reusability)。不同的语言实现闭包的方式不同,Python 是以函数对象为基础,为闭包这一语法结构提供支持的。

函数对象

函数在 Python 中是第一类对象,即函数可以作为一个变量的值,也可以作为另一个函数的参数或返回值。除此之外,函数还可以嵌套定义。需要注意的是,函数对象也有其作用域。

把函数当作数据处理时,它将显式地携带与定义该函数的周围环境的相关信息。这将影响到函数中自由变量的绑定方式。例如,在 foo.py 文件中定义了一个变量 x 和 一个函数 callf

# foo.py
x = 42  # a global variable define in foo.py

def callf(func):
    return func()

现在观察下面这个例子的行为:


>>> import foo
>>> x = 37
>>> def helloworld():
...     return "Hello World. x is %d" % x
...
>>> foo.callf(helloworld)  # 传递一个函数作为参数
'Hello World. x is 37'
>>>

在这个例子中,函数helloworld()使用的 x 的值是在与它相同的环境中定义的(37),即使 helloworld()实际上是在 foo.py 文件中调用的而且 foo.py 也定义了一个变量 x

闭包的概念

维基百科上对闭包(Closure)的解释如下:

闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

对于嵌套函数来说,如果一个内部函数对外部作用域中的变量进行了引用,那么内部函数就被认为是闭包(closure)。内部函数由一个名字(变量)来指代,而这个名字(变量)对于“外层”包含它的函数而言,是本地变量。使用嵌套函数时,闭包将捕获内部函数执行所需的整个环境。

所有函数都拥有两个与闭包相关的重要属性:

  • 指向了定义该函数的全局命名空间的__globals__属性,这始终对应于定义函数的闭包模块。
  • __closure__属性,包含函数引用的除全局变量之外的自由变量信息。
x = 10

def wrap(fun):
    a = 10

    def call(*args, **kwargs):
        return fun(*args, **kwargs) + a
    return call

@wrap
def helloword():
    return 10

print helloword.__globals__
# {'helloword': <function call at 0x02AD82B0>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'F:/exercise/ReportLibTest/closure.py', '__package__': None, 'x': 10, 'wrap': <function wrap at 0x02A10BF0>, '__name__': '__main__', '__doc__': None}

print helloword.__closure__
# (<cell at 0x02AD6190: int object at 0x029C7874>, <cell at 0x02AD61B0: function object at 0x02AD82F0>)

print helloword.__closure__[0].cell_contents
# 10

闭包的实例

  1. 闭包(Closure)最常见的应用场景是装饰器(Decorator)。在下面的例子中,嵌套函数wrapprr和它引用的外部函数的参数func组成了一个闭包。

    def decorator_func(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    
    @decorator_func
    def func(name):
        print 'my name is', name
  2. 如果要编写惰性求值(lazy evaluation)或延迟求值的代码,闭包和嵌套函数特别有用。例如:

    from urllib import urlopen
    
    def page(url):
        def get():
            return urlopen(url).get()
        return get

    在这个例子中,page()函数实际上并不执行任何有意义的计算。它只会创建和返回函数get(),调用该函数时才会获取 Web 页面的内容。因此,get()函数中执行的计算实际上延迟到了程序后面对get()求值的时候。例如:

    python = page('http://www.python.org')
    jython = page('http://www.jython.org')
    
    print(python)  # <function get at 0x95d5f0>
    print(jython)  # <function get at 0x9735f0>
    
    pydata = python()  # 获取 http://www.python.org 页面的内容
    jydata = jython()  # 获取 http://www.jython.org 页面的内容

    在这个例子中,两个变量 pythonjython 实际上是 get() 函数的两个版本,隐式地携带在创建 get() 函数时定义的外部变量的值。

  3. 如果需要在一系列函数调用中保存某个状态,使用闭包是种非常有效的方式。例如考虑下面运行的一个简单的计数器代码:

    def countdown(n):
        count = [n]  # 注意: count 是一个可变对象
        def next():
            r = count[0]
            count[0] -= 1
            return r
        return next
    
    """
    用例
    """
    next = countdown(10)
    while True:
        v = next()  # 获得下一个值
        if not v: 
            break

    在这段代码中,闭包用于保存内部计数器的值count[0]。每次调用内部函数next()时,它都更新并返回这个计数器的前一个值。

使用闭包的注意事项

  1. 在闭包中不能修改外部作用域的变量的值,这与 Python 的作用域规则有关。例如:

    def foo(): 
        m = 0
    
        def foo1(): 
            m = 1  # 定义了一个局部变量,而不是修改了外部作用域的变量的值
            print m 
    
        print m 
        foo1() 
        print m 
    
    foo()
    """
    运行结果
    0
    1
    0
    """

    下面的例子是在 Python 中使用闭包时可能遇到的一段经典的错误代码:

    def foo():
        a = 1
        def foo1():
            a = a + 1  # UnboundLocalError
            return a
        return foo1

    注意:在 Python3 中可以使用 nonlocal 显式的申明一个变量不是闭包的局部变量。

  2. 只有对函数对象进行求值时,才会去找函数体内的变量的值。例如:

    def foo():
        foo_list = []
        for i in range(3):
            def inner_foo(x):
                print(x + i)
            foo_list.append(inner_foo)
        return foo_list
    
    for f in foo():
        print f(2)
    """
    结果是 4 4 4,而不是 2 3 4。
    """
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值