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 中的闭包和自由变量的更多相关文章

  1. 【转】python中的闭包

    转自:http://www.cnblogs.com/ma6174/archive/2013/04/15/3022548.html python中的闭包 什么是闭包? 简单说,闭包就是根据不同的配置信息 ...

  2. 说说Python中的闭包 - Closure

    转载自https://segmentfault.com/a/1190000007321972 Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西 ...

  3. 说说Python中的闭包

    Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西. 闭包的概念 我们尝试从概念上去理解一下闭包. 在一些语言中,在函数中可以(嵌套)定义另一个 ...

  4. Python中的闭包 - Closure

    Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西. 闭包的概念 我们尝试从概念上去理解一下闭包. 在一些语言中,在函数中可以(嵌套)定义另一个 ...

  5. 21.python中的闭包和装饰器

    python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure). 以下说明主要针对 python ...

  6. Python 中的闭包与装饰器

    闭包(closure)是函数式编程的重要的语法结构.闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性. 如果在一个内嵌函数里,对在外部函数内(但不是在全局作用域)的变量进行引用,那么内嵌函数 ...

  7. 轻松理解python中的闭包和装饰器 (下)

    在 上篇 我们讲了python将函数做为返回值和闭包的概念,下面我们继续讲解函数做参数和装饰器,这个功能相当方便实用,可以极大地简化代码,就让我们go on吧! 能接受函数做参数的函数我们称之为高阶函 ...

  8. 轻松理解python中的闭包和装饰器(上)

    继面向对象编程之后函数式编程逐渐火起来了,在python中也同样支持函数式编程,我们平时使用的map, reduce, filter等都是函数式编程的例子.在函数式编程中,函数也作为一个变量存在,对应 ...

  9. Python 中的闭包

    通常来说,函数中的局部变量在函数调用结束的时候不能再被引用,所分配的空间也会被回收. 但是通过闭包这种技术,函数调用结束了,它的局部变量的值还可以保存在闭包里. 试举一例: def make_adde ...

随机推荐

  1. 云图说|DDS读写两步走,带您领略只读节点的风采

    摘要:为了扩展主节点的读请求能力,DDS提供具备独立连接地址的只读节点,适合独立系统直连访问,以缓解大量读请求给主节点造成的压力. 本文分享自华为云社区<[云图说]第235期 DDS读写两步走 ...

  2. Kubernetes 证书默认1年过期时间修改

    使用过的kubeadm搭建K8s集群的朋友知道,默认自动生成的证书有效期只有 1 年,因此需要每年手动更新一次证书,这种形式显然对实际生产环境来说很不友好:因此下面教给大家修改这个过期时间的终极方法. ...

  3. 如何在pyqt中实现win10亚克力效果

    亚克力效果的实现思路 上一篇博客<如何在pyqt中实现窗口磨砂效果> 中实现了win7中的Aero效果,但是和win10的亚克力效果相比,Aero还是差了点内味.所以今天早上又在网上搜了一 ...

  4. ApacheCN Kali Linux 译文集 20211020 更新

    Kali Linux 秘籍 中文版 第一章 安装和启动Kali 第二章 定制 Kali Linux 第三章 高级测试环境 第四章 信息收集 第五章 漏洞评估 第六章 漏洞利用 第七章 权限提升 第八章 ...

  5. 浅谈FFT(快速傅里叶变换)

    前言 啊摸鱼真爽哈哈哈哈哈哈 这个假期努力多更几篇( 理解本算法需对一些< 常 用 >数学概念比较清楚,如复数.虚数.三角函数等(不会的自己查去(其实就是懒得写了(¬︿̫̿¬☆) 整理了一 ...

  6. 「CTSC2006」歌唱王国

    概率生成函数\(g(x)=\sum_{i\geq 0}t_ix^i\),\(t_i\)表示结果为\(i\)的概率 令\(f(x)\)表示i位表示串结束时长度为i的概率,\(G(x)\)表示i位表示串长 ...

  7. iptables简单使用

    1.安装iptables yum install iptables-services 2.iptables简单使用 iptables防火墙文件路径/etc/sysconfig/iptables sys ...

  8. C程序:年转化天

    突然想算算自己到底活了多少天了,e,就是纯属为了好玩,毕竟咱作为一名C初学者还是要多练练的- 为了好玩,加了个密码登陆的,密码是521,还有就是不太懂时间获取... 具体闰年的判断方法: 代码如下: ...

  9. httpclient源码分析之 PoolingHttpClientConnectionManager 获取连接 (转)

    PoolingHttpClientConnectionManager是一个HttpClientConnection的连接池,可以为多线程提供并发请求服务.主要作用就是分配连接,回收连接等.同一个rou ...

  10. Linux-标准输入标准输出

    标准输入(代码为0) 标准输出(代码为1) 标准错误输出(代码为2) 将标准输出重定向到一个文件 find /etc -name fileA >list 等同于 find /etc -name ...