资讯专栏INFORMATION COLUMN

Python Closure

n7then / 1862人阅读

摘要:在计算机科学中,闭包又称词法闭包或函数闭包,是引用了自由变量的函数。闭包被广泛应用于函数式语言中。运用闭包可以避免对全局变量的使用。将栈顶的元素取出,创建元组,并将该元组进栈。

在计算机科学中,闭包 又称 词法闭包 或 函数闭包,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。闭包被广泛应用于函数式语言中。

从上面这段话中可以看出闭包的两个重要条件是引用自由变量函数,与闭包这个名称结合起来看,这个函数就像是一个包,而这个函数所引用的变量就好比函数这个包中封闭起来的东西,包中的东西被紧紧封闭在包中,函数所引用的变量也被与这个函数所绑定。

首先来看两个概念 Nonlocal variable 和 Nested function

Nonlocal variable & Nested function

Nonlocal variable是相对于某个函数来说的,指的是这个函数所调用的在本函数作用域之外的变量,Nested function指的被定义在一个函数(outer enclosing function)中的函数,这个nested function可以调用包围它的作用域中的变量。

看一个例子

def print_msg(msg):
    # outer enclosing function

    def printer():
        # nested function
        print(msg)

    printer()

>>> print_msg("Hello")
Hello

在这个例子中函数printer就是一个nested function,而变量msg就是一个nonlocal variable。这里需要注意的是,printer虽然可以访问msg,但是不可以改变它,如果尝试更改会出现UnboundLocalError: local variable "msg" referenced before assignment

def print_msg(msg):
    def printer():
        msg += "a"
        print(msg)
    printer()

>>> print_msg("Hello")
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 5, in print_msg
  File "", line 3, in printer
UnboundLocalError: local variable "msg" referenced before assignment
local variable "msg" referenced before assignment

如果必须要更改这个变量的值,在Python3中新引入的nonlocal语句可以解决。

def print_msg(msg):
    def printer():
        nonlocal msg
        msg += "a"
        print(msg)
    printer()

>>> print_msg("Hello")
Helloa

在Python2中使用global也可解决,但是global会直接查找全局变量,而nonlocal则是按优先级从本地-->全局进行搜索。

闭包函数

下面使外层函数(outer enclosing function)返回一个函数

def print_msg(msg):
    def printer():
        print(msg)
    return printer

>>> another = print_msg("Hello")
>>> another()
Hello

print_msg("Hello")返回的函数赋值给another,再调用another函数时,发现已经离开了print_msg函数的作用域,但是"Hello"已经被绑定给another,所以仍然能够正常调用,这就是Python中的闭包。

删除print_msg之后,another仍然能够正常调用。

>>> del print_msg
>>> print_msg("Hello")
Traceback (most recent call last):
  File "", line 1, in 
NameError: name "print_msg" is not defined
name "print_msg" is not defined

>>> another()
Hello
闭包的应用

当符合下面几个条件时就形成了闭包:

有一个Nested function

这个Nested function访问了父函数作用域中的变量

父函数返回了这个Nested function

闭包主要运用在需要讲父函数作用域中的变量绑定到子函数的场景之中,在释放掉父函数之后子函数也不会受到影响。运用闭包可以避免对全局变量的使用。对于一个只有需要实现少数方法的类我们也可以用闭包来替代,这样做可以减少资源的使用。

下面需要用类定义不同动物的叫声

class Animal:
    def __init__(self, animal):
        self.animal = animal
    def sing(self, voice):
        return "{} sings {}".format(self.animal, voice)

>>> dog = Animal("dog")
>>> cow = Animal("cow")
>>> dog.sing("wong")
"dog sings wong"
>>> cow.sing("mow")
cow sings mow"

用闭包替代

def make_sing(animal):
    def make_voice(voice):
        return "{} sings {}".format(animal, voice)
    return make_voice

>>> dog = make_sing("dog")
>>> dog("wong")
"dog sings wong"
>>> cow = make_sing("cow")
>>> cow("mow")
"cow sings mow"
闭包与装饰器

闭包通常用来实现一个通用的功能,Python中的装饰器就是对闭包的一种应用,只不过装饰器中父函数的参数是一个函数,下面这个例子通过装饰器实现了在子函数执行前后输出提示信息。

def make_wrap(func):
    def wrapper(*args):
        print("before function")
        func(*args)
        print("after function")
    return wrapper

@make_wrap
def print_msg(msg):
    print(msg)

>>> print_msg("Hello")
before function
Hello
after function

装饰器也可以进行叠加

def make_another(func):
    def wrapper(*args):
        print("another begin")
        func(*args)
        print("another end")
    return wrapper

@make_another
@make_wrap
def print_msg(msg):
    print(msg)

>>> print_msg("Hello")
another begin
before function
Hello
after function
another end
闭包的内部实现 Code Object

为了了解闭包的内部实现,需要用compile命令得出相应的code object

>>> code_obj = compile("print_msg("Hello")", "", "single")

这里第一个参数是一个可以被execeval解析的模块、语句或者表达式;

第二个参数是用来存放运行时错误的文件;

第三个选择single模式,与前面第一个参数填写的表达式相匹配,如果第一个参数是表达式则需要用eval模式,如果是模块则应该用exec模式。

下面通过discode_obj反编译成助记符

>>> dis.dis(code_obj)
  1           0 LOAD_NAME                0 (print_msg)
              2 LOAD_CONST               0 ("Hello")
              4 CALL_FUNCTION            1
              6 PRINT_EXPR
              8 LOAD_CONST               1 (None)
             10 RETURN_VALUE

Python3中通过__code__访问函数的code object(Python2中为func_code)

>>> print_msg.__code__
", line 1>
Cell Object

cell object用来存储被多个作用域所引用的变量。

比如下面函数中msgprint_msg所引用,也被printer所引用,所以msg会被存在一个cell object

def print_msg(msg):
    def printer():
        print(msg)
    return printer

查看其__closure__属性可以验证我们的想法

>>> print_msg("Hello").__closure__
(,)

尽管这两个引用都被存在同意个cell object,但是他们仍然只在各自的作用域下作用。

闭包分析

首先反编译print_msg

>>> dis.dis(print_msg)
  2           0 LOAD_CLOSURE             0 (msg)
              2 BUILD_TUPLE              1
              4 LOAD_CONST               1 (", line 2>)
              6 LOAD_CONST               2 ("print_msg..printer")
              8 MAKE_FUNCTION            8
             10 STORE_FAST               1 (printer)

  4          12 LOAD_FAST                1 (printer)
             14 RETURN_VALUE

LOAD_CLOSURE 0 (msg)将变量msg进栈。

BUILD_TUPLE 1 将栈顶的元素取出,创建元组,并将该元组push进栈。

LOAD_CONST 1print_msg.__code__.co_consts[1]中取出,为printercode object的地址,将其push进栈。

LOAD_CONST 2print_msg.__code__.co_consts[2]中取出,将其push进栈。

STORE_FAST 1从栈顶取出之前创建的函数对象的地址信息赋给局部变量printer(局部变量名记录在__code__.co_varnames中)
__code__.co_varnames的内容为("msg","printer")

将变量msg(记录在__code__.co_cellvars[0])绑定栈顶的函数对象地址。

LOAD_FAST 1msg的值压入栈。

RETURN_VALUE返回栈顶。

可以看到在STORE_FAST 1中将变量msg绑定到了printer函数,从而达到了闭包中内部函数访问外部函数变量的效果。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38562.html

相关文章

  • Python 自定义函数的特殊属性(收藏专用)

    Python 中通过函数定义所创建的用户自定义函数对象均具有一些特殊属性,需要注意的是这里介绍的是自定义函数(function类型)的特殊属性,而非方法(method 类型)的特殊属性,函数和方法的特熟属性以及默认的返回值可能不尽相同。 对于大多数特殊属性,可以通过下面这个例子示范一下: class Test(): def func(self, v = dog): 这里演...

    zhou_you 评论0 收藏0
  • PyTips 0x04 - Python 闭包与作用域

    摘要:项目地址闭包在计算机科学中,闭包英语,又称词法闭包或函数闭包,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。 项目地址:https://git.io/pytips 闭包(Closure) 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是...

    leejan97 评论0 收藏0
  • 说说Python中的闭包 - Closure

    摘要:闭包可以用来在一个函数与一组私有变量之间创建关联关系。夹带私货外部变量返回的是函数,带私货的函数支持将函数当成对象使用的编程语言,一般都支持闭包。所以说当你的装饰器需要自定义参数时,一般都会形成闭包。 Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西。 闭包的概念 我们尝试从概念上去理解一下闭包。 在一些语言中,在函数中可以(嵌...

    leon 评论0 收藏0
  • python基础知识之函数初阶——闭包

    摘要:我们说触发了闭包的函数叫做闭包函数闭包最大的特点就是它可以被外层函数返回后赋值给一个变量,并且携带了外层函数内定义的变量例子如下变量为函数开辟的局部命名空间内定义的变量函数内引用了变量的内层函数名被当作返回值,此时闭包规则达成。 什么是闭包? 其实我们在使用函数过程中不经意间就会触发闭包,因为总会出于某种原因会在函数内引用或修改上一层函数的变量,这时就会触发闭包 那么什么是闭包?其实就...

    TIGERB 评论0 收藏0
  • Python - 闭包Closure

    摘要:恩如期来啦闭包一函数作为返回值介绍闭包之前,先了解一下函数作为返回值的情况。例如之前介绍的装饰器中,就出现了将函数作为返回值。当执行时,相当于执行,且包含。允许使用关键字创造匿名函数。例如调用默认可以把匿名函数作为返回值返回,例如 恩~ 如期来啦闭包~ 一、函数作为返回值 介绍闭包之前,先了解一下函数作为返回值的情况。高阶函数除了可以接收函数作为参数外,还可以把函数作为结果值返回。...

    Jason_Geng 评论0 收藏0

发表评论

0条评论

n7then

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<