@Author : Roger TX (425144880@qq.com)
@Link : https://github.com/paotong999
一、Python 闭包
1、函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:
def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
当我们调用 lazy_sum()
时,返回的并不是求和结果,而是求和函数。
在这里,我们在函数 lazy_sum
中又定义了函数 sum
,并且,内部函数 sum
可以引用外部函数 lazy_sum
的参数和局部变量,当 lazy_sum
返回函数 sum
时,相关参数和变量都保存在返回的函数中,这种称为 闭包(Closure)
的程序结构拥有极大的威力。
2、闭包
在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)
函数:
- 函数只是一段可执行代码,编译后“固化”,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数。
如果函数名后紧跟一对括号,相当于现在我就要调用这个函数
如果函数名后不跟括号,相当于只是一个函数的名字,里面存了函数所在位置的引用
- 在函数式编程语言中,函数是一等公民,函数可以作为另一个函数的参数或返回值,可以赋给一个变量。
(First class value:第一类对象,我们不需要像命令式语言中那样借助函数指针,委托操作函数)
- 函数可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。
引用环境:
- 当内嵌函数体内引用到体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体返回
注意事项:
- 闭包中无法修改外部作用域的局部变量
def foo():
m = 0
def foo1():
m = 1
print (m) # m = 1
print (m) # m = 0
foo1()
print (m) # m = 0
foo()
- 在闭包中,所有在赋值语句左面的变量都是局部变量
def foo():
a = 1
def bar():
a = a + 1
return a
return bar
运行时,会报错
UnboundLocalError: local variable 'a' referenced before assignment
,变量a在赋值语句左边被认为是局部变量,会在bar()
中去找在赋值语句右面的a的值,而找到上层的变量a是不允许在局部内被修改
- 修改赋值和返回,将a改为b
- 将a设定为一个容器,
a = [1]
,a[0] = a[0] + 1
- 在
a = a + 1
之前,使用语句nonloacal a就可以指定a不是闭包的局部变量,需要向上一层变量空间找这个变量。
- 返回闭包时,返回函数不要引用任何循环变量,或者后续会发生变化的变量
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count() # 调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是9,9,9
闭包的应用:
- 坐标连续移动
origin = [0, 0] # 坐标系统原点
legal_x = [0, 50] # x轴方向的合法坐标
legal_y = [0, 50] # y轴方向的合法坐标
def create(pos=origin):
def player(direction,step):
# 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
# 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了
new_x = pos[0] + direction[0]*step
new_y = pos[1] + direction[1]*step
pos[0] = new_x
pos[1] = new_y
#注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
return pos
return player
player = create() # 创建棋子player,起点为原点
print (player([1,0],10)) # 向x轴正方向移动10步,结果为[10, 0]
print (player([0,1],20)) # 向y轴正方向移动20步,结果为[10, 20]
print (player([-1,0],10)) # 向x轴负方向移动10步,结果为[0, 20]
- 取得文件"result.txt"中含有"pass"关键字的行
def make_filter(keep):
def the_filter(file_name):
file = open(file_name)
lines = file.readlines()
file.close()
filter_doc = [i for i in lines if keep in i]
return filter_doc
return the_filter
filter = make_filter("yield")
filter_result = filter("example.py")
print(filter_result)
- 装饰器
python中的使用@语法实现的单例模式就是利用闭包实现的,只不过用了@作为语法糖,使写法更简洁,闭包函数将函数的唯一实例保存在它内部的__closure__
属性中,在再次创建函数实例时,闭包检查该函数实例已存在自己的属性中,不会再让他创建新的实例,而是将现有的实例返给它。
__closure__
属性返回的是一个元组对象,包含了闭包引用的外部变量。
- 若主函数内的闭包不引用外部变量,就不存在闭包,主函数的
_closure__
属性永远为None- 若主函数没有return子函数,就不存在闭包,主函数不存在
_closure__
属性,抛出异常
二、Python 装饰器
装饰器分为:不带参数的装饰器 和 带参数的装饰器
本质上,decorator就是一个返回函数的高阶函数,实现了以下两步
- 将被修饰的函数(函数 B)作为参数传给 @ 符号引用的函数(函数 A)
- 将函数 B 替换(装饰)成第 1 步的返回值
1、不带参数的装饰器
@a_decorator
def f(...):
经过a_decorator后, 函数f就相当于以f为参数调用a_decorator返回结果
f = a_decorator(f)
def makeBold(fn):
print("makeBold"*2)
def wrapped1(): #注意为了演示结果这里讲wrapped函数,分为wrapped1,wrapped2
print("makeBold"*3)
print("asd" + fn() + "asd")
return wrapped1
def makeItalic(fn):
print("makeItalic"*2)
def wrapped2(): #注意为了演示结果这里讲wrapped函数,分为wrapped1,wrapped2
print("makeItalic" *3)
return "zzz" + fn() + "zzz"
return wrapped2
#使用两个装饰器同时装饰一个函数,可以三个,甚至多个。原理一样
@makeBold #其效果等同于执行test_B_I=makeBold(makeItalic(test_B_I))
@makeItalic #其效果等同于执行test_B_I=makeItalic(test_B_I)
def test_B_I():
print("test_B_I"*2)
return "this is the test_B_I"
2、带参数的装饰器
这里就给一个式子, 剩下的问题可以自己去想
@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
这个式子相当于
func = decomaker(argA, argB, ...)(func)
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2015-3-25')
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
now = log('execute')(now)
我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数
>>> now()
execute now():
2015-3-25
>>> print(now.__name__)
'wrapper'
函数也是对象,它有 __name__
等属性,但你去看经过decorator装饰之后的函数,它们的 __name__
已经从原来的 now
变成了 wrapper
:
- 需要把原始函数的
__name__
等属性复制到wrapper()
函数中,需要使用Python内置的functools.wraps
装饰器 - 需要导入
functools
模块。然后在定义wrapper()
的前面加上@functools.wraps(func)
即可
3、装饰器类
在Python中, 其实函数也是对象。 反过来, 对象其实也可以像函数一样调用,只要在类的方法中实现 __call__
方法,就可以实现装饰器类。
装饰器要求接受一个callable对象,并返回一个callable对象。那么用类来实现也是也可以的。我们可以让类的构造函数 __init__()
接受一个函数,然后重载 __call__()
并返回一个函数,也可以达到装饰器函数的效果。
class logging(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print "function: {func}()".format(func=self.func.__name__)
return self.func(*args, **kwargs)
@logging
def say(something):
print "say {}!".format(something)
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载 __call__
方法是就需要接受一个函数并返回一个函数。
class logging(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func): # 接受函数
def wrapper(*args, **kwargs):
print "{level}: {func}()".format(level=self.level, func=func.__name__)
func(*args, **kwargs)
return wrapper #返回函数
@logging(level='INFO')
def say(something):
print "say {}!".format(something)
4、多个装饰器
关于多个装饰器修饰一个函数
- 当一个函数被多个装饰器装饰时,装饰器的加载顺序是从内到外的(从下往上的)。其实很好理解:装饰器是给函数装饰的,所以要从靠近函数的装饰器开始从内往外加载
- 外层的装饰器,是给里层装饰器装饰后的结果进行装饰。相当于外层的装饰器装饰的函数是里层装饰器的装饰原函数后的结果函数(装饰后的返回值函数)。