闭包
什么是闭包?
- 1、多层函数嵌套的定义,一般是两个
- 2、往往内部函数还用到了外部函数的参数
我们把外部函数的参数和内部函数组成的对象叫作闭包。
闭包函数的通俗理解:一个函数的内部有定义了一个函数,并返回内部函数的引用,但在内部函数中又使用到了外部函数的参数。
# 使用闭包求一条直线,y = k * x + b
def line(k, b):
def create_y(x): # 内部函数用到了外部函数的参数
print("%d * %d + %d = %d" % (k, x, b, k * x + b))
return create_y
line_1 = line(1, 2) # line_1 = create_y 注意create_y只是一个function对象
line_1(1) # line_1(1) <==> create_y(1)
line_1(2)
1 * 1 + 2 = 3
1 * 2 + 2 = 4
函数、闭包、实例对象的区别?
闭包:
- 在闭包中,既有函数又有数据,而数据是闭包里面独有的数据,不受外界的影响
函数: - 能够完成一定功能,传递的是这个函数的引用,只有功能;
- 函数中需要使用的全局变量在一定程度上是受限制的,因为全局变量不仅仅是一个函数使用的,其他函数也可以使用,一旦修改了会影响到其他函数使用全局变量。
实例对象: - 能够完成很多复杂的功能,因为实例对象继承了从父类到object中的各种方法和属性,所以传递的是很多功能+很多数据(增强版的闭包)
修改闭包里的数据及修改全局变量
# 定义两个全局变量a1,a2
a1 = 100
a2 = 100
# 闭包函数
def func1():
global a1 # 修改全局变量要加global
a1 = 200 # 修改全局变量a1为200
a2 = 200
a3 = 300
a4 = 300
def func2():
nonlocal a3 # 修改外层变量(外部作用域的局部变量时要加 nonlocal)
a3 = 400 # 修改外部作用域里的局部变量
a4 = 400
print("--func2--(a3, a4): (%d, %d)" % (a3, a4))
print("(a3, a4):(%d, %d)" % (a3, a4))
func2()
print("(a3, a4):(%d, %d)" % (a3, a4))
return func2
print("a1 = %d, a2 = %d" % (a1, a2))
f = func1()
print("------------分割线-------------")
f()
print("a1 = %d, a2 = %d" % (a1, a2))
a1 = 100, a2 = 100
(a3, a4):(300, 300)
--func2--(a3, a4): (400, 400)
(a3, a4):(400, 300)
------------分割线-------------
--func2--(a3, a4): (400, 400)
a1 = 200, a2 = 100
使用闭包的一个经典错误代码
def foo():
a = 1
def bar():
# nonlocal a # 解决方法
a = a + 1
return a
return bar
c = foo()
c()
# 错误原因:在执行代码 c = foo()时,python会导入全部的闭包函数体bar()来分析其的局部变量,
# python规则指定所有在赋值语句左面的变量都是局部变量,则在闭包bar()中,变量a在赋值符号"="的左面,
# 被python认为是bar()中的局部变量。再接下来执行c()时,程序运行至a = a + 1时,因为先前已经
# 把a归为bar()中的局部变量,所以python会在bar()中去找在赋值语句右面的a的值,结果找不到,就会报错。
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-9-de35e9b5ae72> in <module>()
8
9 c = foo()
---> 10 c()
11
12 # 错误原因:在执行代码 c = foo()时,python会导入全部的闭包函数体bar()来分析其的局部变量,
<ipython-input-9-de35e9b5ae72> in bar()
3 def bar():
4 # nonlocal a # 解决方法
----> 5 a = a + 1
6 return a
7 return bar
UnboundLocalError: local variable 'a' referenced before assignment
装饰器
在调用函数时能够对函数的源码不进行修改的前提下进行功能的修改。
# 装饰器的模板:
def set_func(func): # 闭包的外层函数传递的不在是参数,而是函数的引用
def call_func():
print("一些功能...")
func()
return call_func
@set_func
def test(): # <==> test = set_func(test) <==> test = call_func
print("我是test函数")
test() # <==> call_func()
一些功能...
我是test函数
不定长参数的函数装饰器,这里将会使用到 * 和 ** 的打包和解包
def set_func(func):
print("开始装饰函数")
def call_func(*args, **kwargs):
print("一些功能...")
func(*args, **kwargs)
return call_func
@set_func
def test1(num, *args, **kwargs):
print("---num---: %d" % num)
print("---args---:", args)
print("---kwargs---:", kwargs)
test1(100)
print("-------分割线------")
test1(100, 200, 300, a=400, b=500)
开始装饰函数
一些功能...
---num---: 100
---args---: ()
---kwargs---: {}
-------分割线------
一些功能...
---num---: 100
---args---: (200, 300)
---kwargs---: {'a': 400, 'b': 500}
对有返回值的函数进行装饰
def set_func(func):
print("开始装饰函数")
def call_func(*args, **kwargs):
print("一些功能...")
return func(*args, **kwargs)
return call_func
@set_func
def test2(num, *args, **kwargs):
print("---num---: %d" % num)
print("---args---:", args)
print("---kwargs---:", kwargs)
return "ok"
res = test2(100)
print(res)
print("-------分割线------")
res2 = test2(100, 200, 300, a=400, b=500)
print(res2)
开始装饰函数
一些功能...
---num---: 100
---args---: ()
---kwargs---: {}
ok
-------分割线------
一些功能...
---num---: 100
---args---: (200, 300)
---kwargs---: {'a': 400, 'b': 500}
ok
所以,最通用的装饰器如下,如果func函数没有返回值,就返回None,并没有影响。
def set_func(func):
print("开始装饰函数")
def call_func(*args, **kwargs):
print("一些功能...")
return func(*args, **kwargs)
return call_func
多个装饰器对一个函数进行装饰
@装饰器1
@装饰器2
def func():
注意:
- 1、装饰的时候先执行装饰2然后执行装饰器1
- 2、功能实现的时候,谁在上面就先实现他的功能,即,先装饰器1的功能然后装饰器2的功能
def set_func1(func):
print("装饰器1:开始装饰函数")
def call_func1(*args, **kwargs):
print("装饰器1:一些功能...")
return func(*args, **kwargs)
return call_func1
def set_func2(func):
print("装饰器2:开始装饰函数")
def call_func2(*args, **kwargs):
print("装饰器2:一些功能...")
return func(*args, **kwargs)
return call_func2
@set_func1 # 接着执行这个装饰器, call_func2 = set_func(set_func2) ==> call_func2 = call_func1
@set_func2 # 先执行这个装饰器, test3 = set_func2(test3) ==> test3 = call_func2
def test3():
print("我是test3")
test3() # <==> test3() = set_func2() = set_func1()--(箭头)
# |----<----| |---------<----------|
装饰器2:开始装饰函数
装饰器1:开始装饰函数
装饰器1:一些功能...
装饰器2:一些功能...
我是test3
从上面的例子的执行结果中可以看出,装饰过程是先执行装饰器2然后执行装饰器1,但在功能实现上则是先执行装饰器1的功能,然后执行装饰器2的功能。
总结:装饰器的装饰过程是由里到外,功能实现过程是由外到里
以上都是装饰器的最基本的一些应用,包括:对普通函数的装饰,对带不定长参数函数的装饰,对带返回值函数的装饰以及多个装饰器对一个函数进行装饰,并给出了一个通用的装饰器模型。下面再给大家介绍两种装饰器,一个是类装饰器,一个是带参数的装饰。
首先是类装饰器,结合装饰器的运行过程,对类对象的设计就很简单了。
# python运行这段函数的执行过程
def set_func(func): # 1、函数定义,开辟一个内存空间,名字为set_func,接收一个参数为func
print("开始装饰函数") # 3、打印
def call_func(*args, **kwargs): # 4、函数定义,开辟一个内存空间,名字为call_func,接收不定长参数和关键字参数
print("一些功能...") # 8、打印
return func(*args, **kwargs) # 9、执行func函数,即执行test函数,12、返回test函数返回过来的结果,返回到第7步
return call_func # 5、返回call_func的函数引用,返回到第2步
@set_func # 2.2、装饰器对函数装饰 <==> test = set_func(test),开始执行set_func函数,6、此时test = call_func
def test(): # 2.1、函数定义,开辟一个内存空间,名字为test,不接收参数
print("我是test函数") # 10、打印
return "ok" # 11、返回字符串“ok”,返回到第9步
test() # 7、执行test函数,这里的test指向的是装饰器里的call_func函数,所以test() = call_func();13、到这里程序执行完成
熟悉装饰器的运行过程后,在于类对象进行比对,发现过程可以用__init__和__call__方法实现
class Test():
def __init__(self, func):
print("开始装饰函数")
self.func = func
def __call__(self, *args, **kwargs):
print("一些功能...")
return self.func(*args, **kwargs)
@Test
def test4(): # <==> test4 = Test(test4),test4是Test的实例对象
print("我是test4")
return "ok"
res = test4()
print(res) # 实例()==> 执行__call__方法
开始装饰函数
一些功能...
我是test4
ok
最后是带参数的装饰器
应用场景:比如一个网站需要进行权限验证,但是不同的用户可能验证权限不一样,所以在装饰某个业务函数时需要传入不同参数进行不同的权限验证功能。
def set_level(level_num):
def set_func(func):
print("开始装饰")
def call_func(*args, **kwargs):
if level_num == 1:
print("权限验证1")
elif level_num == 2:
print("权限验证2")
return func(*args, **kwargs)
return call_func
return set_func
# python执行到这里是其实就开始装饰了,这就是为什么下面先出现两个“开始装饰的原因”
@set_level(1) # 先执行set_level(1),返回set_func,然后@set_func对test5进行装饰
def test5():
print("我是test5")
return "ok"
@set_level(2)
def test6():
print("我是test6")
return "ok2"
res5 = test5()
print(res5)
print("-----分割线-----")
res6 = test6()
print(res6)
开始装饰
开始装饰
权限验证1
我是test5
ok
-----分割线-----
权限验证2
我是test6
ok2