博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python Closure
阅读量:7136 次
发布时间:2019-06-28

本文共 5181 字,大约阅读时间需要 17 分钟。

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

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

首先来看两个概念 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 printerUnboundLocalError: local variable 'msg' referenced before assignmentlocal 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 definedname '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_wrapdef print_msg(msg):    print(msg)>>> print_msg("Hello")before functionHelloafter function

装饰器也可以进行叠加

def make_another(func):    def wrapper(*args):        print("another begin")        func(*args)        print("another end")    return wrapper@make_another@make_wrapdef print_msg(msg):    print(msg)>>> print_msg("Hello")another beginbefore functionHelloafter functionanother 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函数,从而达到了闭包中内部函数访问外部函数变量的效果。

转载地址:http://pwtrl.baihongyu.com/

你可能感兴趣的文章
poj 3468 A Simple Problem with Integers(线段树+区间更新+区间求和)
查看>>
dup和dup2函数
查看>>
Js的原型和原型链理解
查看>>
未知题目
查看>>
在C#中??和?分别是什么意思?
查看>>
APP 开发,代码写的真烂
查看>>
适合0基础的web开发系列教程-html5新的表单元素
查看>>
Python 常用算法记录
查看>>
OC中的野指针(僵尸指针)
查看>>
SSM环境的搭建
查看>>
leetcode 196. Delete Duplicate Emails
查看>>
聚美第六天
查看>>
Q:java中的泛型数组
查看>>
[Android] adb 命令 dumpsys activity , 用来看 task 中的activity。 (uninstall virus)
查看>>
数据分析学习笔记(三)-NetworkX的使用
查看>>
rm 命令简要
查看>>
xadmin快速搭建后台管理系统
查看>>
MySQL 5.7 分区表性能下降的案例分析
查看>>
遍历文件夹并建成目录树
查看>>
结对项目--四则运算“软件”之升级版
查看>>