由浅入深彻底了解 Python 闭包和装饰器
闭包和nonlocal
闭包的概念
闭包是指在方法内引用方法外定义的非全局变量,如下示例是在内部方法inner使用外部方法outer中的变量num。 示例1:
def outer():
num = 10
def inner():
return num
return inner
test = outer()
test()
调用test()方法返回的num值是10,也就是说在inner中使用的是外部方法outer()中定义的变量num,但是,在调用test()方法时,outer函数已经返回了,本地的作用域也不存在了,是怎么得到num的值的呢?此时便涉及到变量的作用域相关内容。
变量的作用域
大家都知道,变量一般分为局部变量和全局变量,如下例子:
示例2:
num1 = 10
def outer():
num2 = 20
def inner():
global num1
num3 = 30
return num1 + num2 + num3
return inner
test = outer()
test()
其中,num1是全局变量,在inner方法内使用时需要使用global进行声明;num3是局部变量,在inner方法内定义并使用
但是对inner来说,num2这种既非局部变量、也非全局变量的变量的是一种什么变量?我们来看下python文档中对于变量的定义
If a name is bound in a block, it is a local variable of that block, unless declared as nonlocal. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.
根据第三点可以看出,如果一个变量在一个代码块中使用,但是没有在代码块中定义,就是自由变量(未在本地作用域中绑定的变量)。
python在__code__属性中保存局部变量和自由变量的名称,在__closure__属性中保存自由变量的值。
test.__code__.co_varnames
test.__code__.co_freevars
test.__closure__
test.__closure__[0].cell_content
nonlocal
在示例1中,如果我们要在inner内修改num的值,应该怎么做? 示例3
def outer():
num = 10
def inner():
num += 1
return num
return inner
test = outer()
test()
如果直接对num进行自增操作,会发现有报错,local variable 'num' referenced before assignment,也就是说你在inner内使用num += 1时,相当于num = num + 1,此时对num进行赋值python默认num是局部变量,但是inner内并没有定义num,所以会报错。
我们不妨想想还有什么其他方法可以解决示例3的问题,如果把num改成list类型,可不可以呢?我们一起来试试。
示例4:
def outer():
num = [10]
def inner():
num[0] += 1
return num[0]
return inner
test = outer()
test()
通过运行,我们发现可以得到想要的结果,因为list是可变类型,此时num指向的是自由变量num,并对num中的数据进行了改变。
但是这样每次都把不可变数据转换成可变数据进行传递太麻烦了,所以python3引入了nonlocal声明,作用是把变量标记为自由变量,即使在函数中为变量赋予了新值,也会变成自由变量。
示例5:
def outer():
num = 10
def inner():
nonlocal num
num += 1
return num
return inner
test = outer()
test()
python2没有nonlocal,所以需要把变量存储为可变变量的元素或属性,并把对象绑定给自由变量。
装饰器基础
装饰器的概念
对方法进行装饰,在方法运行前后添加其他功能
示例6:
import time
def outer(func):
def inner():
time1 = time.time()
func()
time2 = time.time()
return time2 - time1
return inner
@outer
def test():
print("test")
相当于:test = outer(test),此装饰器就是用来计算方法运行时间的
装饰器的执行顺序
装饰器在被装饰的函数定义之后立即运行,通常是在导入时(即python加载模块时)
示例7:
def outer(func):
print("%s outer running" % func)
def inner():
print("%s inner running" % func)
return func
return inner
@outer
def test1():
print("test1 runing")
@outer
def test2():
print("test2 runing")
def test3():
print("test3 runing")
def main():
print("main runing")
test1()
test2()
test3()
if __name__ == "__main__":
main()
结果为:
<function test1 at 0x104b4bd90> outer running
<function test2 at 0x104b4bea0> outer running
main runing
<function test1 at 0x104b4bd90> inner running
<function test2 at 0x104b4bea0> inner running
test3 runing
可以看出,在函数test1、test2定义后,装饰器outer立即被执行。
参数化装饰器
参数化装饰器即为带参数的装饰器,如果装饰器需要额外的参数,就可以创建一个装饰器工厂函数,把参数传给它,如下例:
示例8:
register = set()
def dec(active=True):
def outer(func):
print("running %s (active=%s)" % (func, active))
if active:
register.add(func)
else:
register.discard(func)
return func
return outer
@dec(active=False)
def test1():
return "runing test1"
@dec()
def test2():
print("runing test2")
def test3():
print("running test3")
def main():
print("main runing")
test1()
test2()
test3()
print(register)
if __name__ == "__main__":
main()
运行结果如下:
running <function test1 at 0x106c71e18> (active=False)
running <function test2 at 0x106c71ea0> (active=True)
main runing
runing test2
running test3
{<function test2 at 0x106c71ea0>}
可以看出,active作为了额外参数传到装饰器outer中对register内的参数进行了控制
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- mongoDB (三) mongoDB分片集群
- mongoDB (四) mongoDB认证
- 一天一大 leet(二叉树中的最大路径和)难度:困难 DAY-21
- 一天一大 leet(把数字翻译成字符串)难度:中等 DAY-9
- 一天一大 leet(模式匹配)难度:中等 DAY-22
- 一天一大 leet(二进制求和)难度:简单 DAY-23
- xmake v2.3.7 发布, 新增 tinyc 和 emscripten 工具链支持
- 一天一大 leet(最接近的三数之和)难度:中等 DAY-24
- Django渲染模板
- 一天一大 leet(移除重复节点)难度:简单 DAY-26
- Spring Boot 项目瘦身指南,瘦到不可思议!
- BigData--Hadoop数据压缩
- 面试官问我什么是JMM
- django+nginx+gunicorn部署配置
- JVM的YGC,这次被它搞惨了!