一、闭包
1、闭包的概念
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。闭包就是能够读取其他函数内部变量的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁
用比较容易懂的人话说,就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包,例如:
1
2
3
4
5
6
7
8
9 def parent():
msg = '局部变量'
def child():
print(msg) # 外部变量
return child # 返回的是函数,带外部变量的
if __name__ == '__main__':
p = parent()
p()
2、闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在被调用后被自动清除。
为什么会这样呢?原因就在于parent是child的父函数,而child被赋给了一个全局变量,这导致child始终在内存中,而child的存在依赖于parent,因此parent也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
3、闭包条件
- 必须是一个嵌套的函数。
- 闭包必须返回嵌套函数。
- 嵌套函数必须引用一个外部的非全局的局部自由变量。
4、优点
- 避免使用全局变量
- 可以提供部分数据的隐藏
- 可以提供更优雅的面向对象实现
5、举个栗子
使用闭包代替类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 用类实现一个加法的类是这样
class Test(object):
def __init__(self, a, b):
self.a = a
self.b = b
def add(self):
return self.a + self.b
# 用闭包实现
def test(a):
def add(b):
return a + b
return add
ad = test(1) # 是不是很像类的实例化
print(ad(1)) # out:2
print(ad(2)) # out:3
print(ad(3)) # out:4栗子1(惰性求值)
1
2
3
4
5
6def foo(msg):
def say():
print("hello " + str(msg))
return say
say = foo("志玲")
say()栗子2(使用闭包保持状态)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def count_down(num):
def next():
nonlocal num
temp = num
num -= 1
return temp
return next
if __name__ == '__main__':
n = count_down(10)
while True:
# 每调用一次就会减少一次计数
value = n()
print(value)
if value == 0:
break栗子3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23"""
比如,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。我以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。
"""
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步
print player([0,1],20) # 向y轴正方向移动20步
print player([-1,0],10) # 向x轴负方向移动10步
4、使用闭包的注意点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成性能问题,甚至有可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
二、装饰器(Decorator)
1、前期基础
1.1、高阶函数
- 说明
- 把一个函数名当做形实传给另外一个函数(不修改被装饰的函数源代码的情况下为其添加功能)
- 返回值中包含函数名(返回值中包含函数名(不修改函数的调用方式))
举个栗子
把函数作为参数传递
1
2
3
4
5
6
7
8def fun():
print("这个是一个函数")
def test1(func):
print(func)
func()
test1(fun)
在函数前面添加代码
1
2
3
4
5
6
7
8
9
10
11def fun():
print("核心代码")
def test1(func):
print('在原始核心代码之前添加功能')
func()
print('在原始核心代码之后添加功能')
if __name__ == '__main__':
test1(fun)
#缺点调用方式发生改变,不能像原来的方法去调用被修饰的函数返回值中包含函数名(不修改函数的调用方式)
1
2
3
4
5
6
7
8def bar():
print("核心代码")
def test3(func):
print('在原始核心代码之前添加功能')
return func
if __name__ == '__main__':
bar = test3(bar)
bar()
1.2、嵌套函数
说明
在一个函数体内,用def去声明一个函数
举个栗子
栗子1(函数嵌套)
1
2
3
4
5
6
7def foo():
print("在核心代码之前调用")
def bar():
print("核心代码")
bar()
if __name__ == '__main__':
foo()栗子2(函数调用)
1
2
3
4
5
6
7
8#注意两者之间的区别
def foo():
print("in the foo")
def bar():
print("in the bar")
bar()
if __name__ == '__main__':
foo()
2、什么是装饰器
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能,也是闭包的一种应用场景
装饰器 = 高阶函数 + 嵌套函数
3、作用
为其他函数添加附加功能
4、原则
不能修改被装饰的函数的源代码,不能修改被装饰的函数的调用方式。
即:装饰器对待被修饰的函数是完全透明的。
5、定义装饰器
定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14def strong(fun): # fun 将来就是被替换的 hello
def new_hello():
print("我是装饰器中的代码, 在 hello 之前执行的")
fun()
print("我是装饰器中的代码, 在 hello 之后...执行的")
return new_hello
def hello():
print("我是 hello 函数中的代码")
if __name__ == '__main__':
# 这里调用的其实是装饰器返回的函数.
hello()
- 说明
- 在需要添加的装饰函数上面一行使用
@
来添加装饰器 @
后面紧跟中装饰器名strong
, 当然你可以定于任何的名字.strong
是装饰器, 本质上是一个函数. 他接收函数hello
作为参数, 并返回一函数来替换掉hello
(当然也可以不替换).hello
使用装饰器之后, 相当于hello
函数使用下面的代码被替换掉了hello = strong(hello)
- 在调用
hello
的时候, 其实是调用的strong()
返回的那个函数.
- 在需要添加的装饰函数上面一行使用
- 满足条件
- 装饰器函数运行在函数定义的时候
- 装饰器需要返回一个可执行的对象
- 装饰器返回的可执行对象要兼容函数f的参数
三、附
1、语法糖
就相当于汉语里的成语。即,用更简练的言语表达较复杂的含义。指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会,例如5*5等同于5+5+5+5+5