Python 中的闭包和自由变量
1.定义
在函数内部再定义一个函数,并且这个函数用到了外部函数的变量(LEGB),最后返回新建函数的函数名索引,那么将这样的能够访问其定义时所在的作用域的函数以及用到的一些变量称之为闭包
。被引用的非全局变量也称为自由变量
。这个自由变量保存在外部函数的只读属性 __closure__
中,会与内层函数产生一个绑定关系,也就是自由变量将不会在内存中轻易消失。如下例所示:
# 计算函数被调用的次数
def counter(FIRST=0):
-----------------__closure__---------------
|cnt = [FIRST] | # 之所以选列表是因为作用域问题,详见后文
| |
|def add_one(): |
| cnt[0] += 1 |
| return cnt[0] |
------------------------------------------
return add_one
# 每当外部函数被调用时,都将重新定义内部的函数,而变量 cnt 的值也可能不同
num5 = counter(5)
num10 = counter(10)
print(num5()) # 6
print(num5()) # 7
print(num10()) # 11
print(num10()) # 12
# 如果这个函数仅仅是嵌套函数,那么它的 __closure__ 应该是 None
print(num5.__closure__) # (<cell at 0x0163FE30: list object at 0x01514A80>,)
print(num5.__closure__[0].cell_contents) # 7
print(num10.__closure__[0].cell_contents) # 12
# 或者通过 __code__.co_freevars 查看函数中是否有自由变量,如果有自由变量,即为闭包
print(num10.__code__.co_freevars) # ('cnt',)
2.nonlocal 关键字
上面代码中的 cnt 变量是一个列表,可变对象,但如果是不可变对象,如:numer、tuple 等呢?
def counter(FIRST=0):
cnt = FIRST # number
def add_one():
cnt += 1
return cnt
return add_one
num5 = counter(5)
print(num5.__closure__)
print(num5.__code__.co_freevars)
print(num5())
----------------------------------------------------------------------------
def counter(FIRST=0):
cnt = (FIRST,) # tuple
def add_one():
cnt[0] += 1
return cnt[0]
return add_one
num5 = counter(5)
print(num5.__closure__)
print(num5.__code__.co_freevars)
print(num5())
以上实例输出结果:
None
()
Traceback (most recent call last):
File "test.py", line, in <module>
print(num5())
File "test.py", line, in add_one
cnt += 1
UnboundLocalError: local variable 'cnt' referenced before assignment
----------------------------------------------------------------------------
(<cell at 0x0180FE10: tuple object at 0x0173A750>,)
('cnt',)
Traceback (most recent call last):
File "test.py", line, in <module>
print(num5())
File "test.py", line, in add_one
cnt[0] += 1
TypeError: 'tuple' object does not support item assignment
可以看出,此时 cnt 不再是自由变量,而是变成了局部变量,且提示 UnboundLocalError 未绑定局部错误。为什么不是自由变量了呢?为什么列表就没问题呢?
这是因为 Python 中并没有要求先声明一个变量才能使用它,Python 解释器认为:在函数体内,只要对一个变量进行赋值操作,那么这个变量就是局部变量。
Python的模块代码执行之前,并不会经过预编译,模块内的函数体代码在运行前会经过预编译,因此不管变量名的绑定发生在作用域的那个位置,都能被编译器知道。
而 cnt += 1 相当于 cnt = cnt + 1,对 cnt 进行了赋值操作,所以 Python 解释器认为 cnt 是函数内的局部变量,但是执行的时候,先执行 cnt+1 时发现:
因为先前已经认定 cnt 为局部变量了,现在在局部作用域内找不到 cnt 的值,也不会再到外部作用域找了,就会报错。所以说现在 cnt 已经不是自由变量了。
那么 tuple 类型的 cnt 呢?首先 cnt[0] = cnt[0] + 1,虽然有赋值,但是其左边也是 cnt[0],cnt 是从外边作用域索引了的。
所以,你看它显示的结果:此时,cnt 确实也是自由变量的,但是它是不可变对象啊,所以报了 TypeError 错误。这下列表为什么行,你应该知道了。
或者你使用 nonolocal
关键字,这个关键字的用法与 global 很像,让你能够给外部作用域(非全局作用域)内的变量赋值。它可以使得一个被赋值的局部变量变为自由变量,并且 nonlocal
声明的变量发生变化时,__closure__
中存储的值也会发生变化:
def counter(FIRST=0):
cnt = FIRST # number
def add_one():
nonlocal cnt
cnt += 1
return cnt
return add_one
num5 = counter(5)
print(num5.__closure__)
print(num5.__code__.co_freevars)
print(num5())
(<cell at 0x01BFFE30: int object at 0x53E064D0>,)
('cnt',)
6
nonlocal 和 global
def scope_test():
spam = "test spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
do_nonlocal()
print("After nonlocal assignment:", spam) # nonlocal spam
do_global()
print("After global assignment:", spam) # nonlocal spam
scope_test()
print("In global scope:", spam) # global spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
3.注意事项
lambda 自由参数之坑,特别是和列表解析或for循环结合使用时。lambda para_list : expression == > def (para_list): return expression
#---CASE1
fs = [lambda j:i*j for i in range(3)]
print([f(2) for f in fs])
#---CASE2
fs = map(lambda i:(lambda j: i*j), range(3))
print([f(2) for f in fs])
#---CASE3
fs = [(lambda i:lambda j:i*j)(i) for i in range(3)]
print([f(2) for f in fs])
[4, 4, 4]
[0, 2, 4]
[0, 2, 4]
首先,CASE1 和 CASE3 显然都是每循环一次,就添加一个 lambda 函数到列表中,不同的是,CASE1 添加的 lambda 函数中的 i 每次并没有接收 for 循环中 i 的值,它只是定义的时候指了下 i,所以说,CASE1 中的几个 lambda 函数的 i,是最后调用的时候,也就是 f(2) 时才到外层作用域找它的值的,此时找到的 i 的值就是里面 for 循环结束时的 i 的值。CASE3 则是一开始定义、添加的时候就给 i 赋好了初值。CASE2 则是因为 map 每次迭代的时候都会将一个可迭代对象的元素传给了 i,所以 CASE2 里面的每个 lambda 函数的 i 也是各有各的值的。
像这种 lambda 的自由参数的问题的话,如果你不是故意这么做的话,还是转为默认参数的好:
fs = [lambda x: x+i for i in range(3)]
print([f(2) for f in fs])
fs = [lambda x, i=i: x+i for i in range(3)]
print([f(2) for f in fs])
[4, 4, 4]
[2, 3, 4]
另外,就是列表解析里面的作用域是一个全新的作用域,和普通的 for 循环则有所不同:
#---CASE4
fs = [lambda j:i*j for i in range(3)]
print([f(2) for f in fs])
i = 4
print([f(2) for f in fs])
#---CASE5
fs = []
for i in range(3):
fs.append(lambda j:i*j)
print([f(2) for f in fs])
i = 4
print([f(2) for f in fs])
[10, 10, 10]
[10, 10, 10]
[10, 10, 10]
[8, 8, 8]
4.使用场景
装饰器
惰性求值,比较常见的是在数据库访问的时候,可参考 Django 的 queryset 的实现
需要对某个函数的参数提前赋值的情况;当然也可以使用 functools.parial 的偏函数:functools.partial(func, *args, **kw),返回一个 partial 函数对象。
# y = a*x + b, a 和 b 可能只出现一次, x 会出现多次
def line(a, b, x):
return a*x + b
print(line(3, 4, 5))
print(line(3, 4, 6))
print(line(7, 4, 5))
print(line(7, 4, 6))
# 2.使用闭包
def line(a, b):
def value(x):
return a*x + b
return value
# y = 3x + 4
line1 = line(3, 4)
print(line1(5))
print(line1(6))
print(line1(7))
# y = 9x + 7
line2 = line(9, 7)
print(line2(5))
print(line2(6))
print(line2(7))
# 3.使用 functools.partial 偏函数
from functools import partial
line3 = partial(line, 3)
print(line3) # functools.partial(<function line at 0x011237C8>, 3)
print(line3(4, 5))
line4 = partial(line, 3, 4)
print(line4(5))
print(line4(6))
print(line4(7))
line5 = partial(line, 9, 7)
print(line5(5))
print(line5(6))
print(line5(7))
简单总结functools.partial
的作用就是:其能把一个函数的某些参数给固定住(也就是设置默认值),并返回一个新的函数,调用这个新函数会更简单。
···
Python 中的闭包和自由变量的更多相关文章
- 【转】python中的闭包
转自:http://www.cnblogs.com/ma6174/archive/2013/04/15/3022548.html python中的闭包 什么是闭包? 简单说,闭包就是根据不同的配置信息 ...
- 说说Python中的闭包 - Closure
转载自https://segmentfault.com/a/1190000007321972 Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西 ...
- 说说Python中的闭包
Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西. 闭包的概念 我们尝试从概念上去理解一下闭包. 在一些语言中,在函数中可以(嵌套)定义另一个 ...
- Python中的闭包 - Closure
Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西. 闭包的概念 我们尝试从概念上去理解一下闭包. 在一些语言中,在函数中可以(嵌套)定义另一个 ...
- 21.python中的闭包和装饰器
python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure). 以下说明主要针对 python ...
- Python 中的闭包与装饰器
闭包(closure)是函数式编程的重要的语法结构.闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性. 如果在一个内嵌函数里,对在外部函数内(但不是在全局作用域)的变量进行引用,那么内嵌函数 ...
- 轻松理解python中的闭包和装饰器 (下)
在 上篇 我们讲了python将函数做为返回值和闭包的概念,下面我们继续讲解函数做参数和装饰器,这个功能相当方便实用,可以极大地简化代码,就让我们go on吧! 能接受函数做参数的函数我们称之为高阶函 ...
- 轻松理解python中的闭包和装饰器(上)
继面向对象编程之后函数式编程逐渐火起来了,在python中也同样支持函数式编程,我们平时使用的map, reduce, filter等都是函数式编程的例子.在函数式编程中,函数也作为一个变量存在,对应 ...
- Python 中的闭包
通常来说,函数中的局部变量在函数调用结束的时候不能再被引用,所分配的空间也会被回收. 但是通过闭包这种技术,函数调用结束了,它的局部变量的值还可以保存在闭包里. 试举一例: def make_adde ...
随机推荐
- golang中的tcp编程
1. tcp server package main import ( "bufio" "fmt" "net" ) func main() ...
- Cobbler批量安装操作系统
1,关闭selinux getenforce 查看selinux状态 Disabled 修改/etc/selinux/config 文件 将SELINUX=enforcing改为SELINUX=di ...
- 用python设计猜大小的游戏
import random def roll_dice(numbers = 3,points = None): print("------摇骰子------") if points ...
- linux中wc命令
目录 一:linux中wc命令 1.wc命令介绍 2.wc命令作用 3.wc命令格式 4.参数 5.解析案例 一:linux中wc命令 1.wc命令介绍 Linux wc命令用于计算字数. 利用wc指 ...
- netstat in Linux
# netstat -unlt Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address ...
- 如何在pyqt中给无边框窗口添加DWM环绕阴影
前言 在之前的博客<如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果>中,我们实现了窗口的亚克力效果,同时也用SetWindowC ...
- 使用Typora+PicGo实现图片自动上传到Gitee图床
一.前言 我们在使用Typora编辑器时,会加上图片,有个弊端,只能在本地访问,你发送给别人就无法查看图片,当然可以导出pdf.小编这边的需求是这样的,自己搭建的一个博客系统,基于Hexo搭建的,这个 ...
- httpclient源码分析之 PoolingHttpClientConnectionManager 获取连接 (转)
PoolingHttpClientConnectionManager是一个HttpClientConnection的连接池,可以为多线程提供并发请求服务.主要作用就是分配连接,回收连接等.同一个rou ...
- MySQL-MMM高可用架构
MySQL-MMM高可用架构 目录 MySQL-MMM高可用架构 一.MMM 1. MMM的概述 2. MMM的应用场景 3. MMM的特点 4. 关于MMM高可用架构的说明 5. 用户及授权 二.案 ...
- 「 题解 」P2487 [SDOI2011]拦截导弹
简单题意 给定 \(n\) 个数对 \((h_i, v_i)\). 求: 最长不上升子序列的长度. 对于每个 \(i\),分别求出包含数对 \((h_i, v_i)\) 的最长上升子序列的个数和最长不 ...