在学习装饰器之前我们先了解下什么是闭包。
闭包
- 函数引用
def test1():
print("--- in test1 func----")
# 调用函数
test1()
# 引用函数
ret = test1
print(id(ret))
print(id(test1))
#通过引用调用函数
ret()
运行结果:
--- in test1 func----
140212571149040
140212571149040
--- in test1 func----
我们发现 test1() 和 test1 是有区别的,前者是直接调用函数,后者只是函数的引用.
- 闭包
# 定义一个函数
def test(number):
# 在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包
def test_in(number_in):
print("in test_in 函数, number_in is %d" % number_in)
return number+number_in
# 其实这里返回的就是闭包的结果
return test_in
# 给test函数赋值,这个20就是给参数number,此时ret是 test_in 的引用
ret = test(20)
# 注意这里的100其实给参数number_in
print(ret(100))
#注 意这里的200其实给参数number_in
print(ret(200))
运行结果:
in test_in 函数, number_in is 100
120
in test_in 函数, number_in is 200
220
闭包实例
假如我们要求直线 y=ax+b 的长度.
# 第1种
k = 1
b = 2
y = k*x+b
# 缺点:如果需要多次计算,那么就的写多次y = k*x+b这样的式子
y = k * 1 + b
y = k * 2 + b
# 第2种
def line_2(k, b, x):
print(k*x+b)
line_2(1, 2, 0)
line_2(1, 2, 1)
line_2(1, 2, 2)
line_2(3,4,0)
line_2(3,4,1)
line_2(3,4,2)
# 缺点:如果想要计算多次这条线上的y值,那么每次都需要传递k,b的值,麻烦
print("-"*50)
# 第3种: 全局变量
k = 1
b = 2
def line_3(x):
print(k*x+b)
line_3(0)
line_3(1)
line_3(2)
k = 11
b = 22
line_3(0)
line_3(1)
line_3(2)
# 缺点:如果要计算多条线上的y值,那么需要每次对全局变量进行修改,代码会增多,麻烦
print("-"*50)
# 第4种:缺省参数
def line_4(x, k=1, b=2):
print(k*x+b)
line_4(0)
line_4(1)
line_4(2)
line_4(0, k=11, b=22)
line_4(1, k=11, b=22)
line_4(2, k=11, b=22)
# 优点:比全局变量的方式好在:k, b是函数line_4的一部分 而不是全局变量,因为全局变量可以任意的被其他函数所修改
# 缺点:如果要计算多条线上的y值,那么需要在调用的时候进行传递参数,麻烦
print("-"*50)
# 第5种:实例对象
class Line5(object):
def __init__(self, k, b):
self.k = k
self.b = b
def __call__(self, x):
print(self.k * x + self.b)
line5_1_2 = Line5(1, 2)
# 对象.方法()
# 对象()
line5_1_2(0)
line5_1_2(1)
line5_1_2(2)
line5_11_22 = Line5(11, 22)
line5_11_22(0)
line5_11_22(1)
line5_11_22(2)
line5_1_2(3)
# 缺点:为了计算多条线上的y值,所以需要保存多个k, b的值,因此用了很多个实例对象, 浪费资源
print("-"*50)
# 第6种:闭包
def line_6(k, b):
def create_y(x):
print(k*x+b)
return create_y
line_6_1 = line_6(1, 2)
line_6_1(0)
line_6_1(1)
line_6_1(2)
line_6_2 = line_6(11, 22)
line_6_2(0)
line_6_2(1)
line_6_2(2)
可以发现:
- 匿名函数能够完成基本的简单功能,只有简单功能.
- 普通函数能够完成较为复杂的功能,只有功能.
- 闭包能够完成较为复杂的功能,包含功能和数据,传递的是数据+功能(即外层函数能保存接收的数据给里面的函数使用)
- 对象能够完成最复杂的功能,包含很多数据和很多功能,传递的是数据+功能
- 闭包也具有提高代码可复用性的作用.
- 注意:由于闭包引用了外部函数的局部变量,导致外部函数的局部变量没有及时释放而消耗内存.
修改外部函数中的变量
# 使用关键字 nonlocal 即可修改外部函数的变量
def func1(a):
def func2():
nonlocal a
a += 1
return a
return func2
装饰器
装饰器本质上就是闭包,装饰器在不改变原函数的定义的条件下,给原函数扩展功能.
下面我们来分析这段代码:
1 def out(func):
2 def inner():
3 print('正在装饰')
4 func()
5 return inner
6 @out
7 def test():
8 print('我被装饰啦!')
9 test()
func 的执行调用过程
- 第1行:out 装饰器函数
- 第6行:底层实现 test = out(test) ,不会执行,需要等待
- 第7行:定义 func 函数对象
- 第6行:先执行右边,在执行左边.func-->test,test-->inner
- 第9行:test(),本质上调用 inner()
- 第3行
- 第4行
- 第8行
多个装饰器装饰一个函数
def message(func):
print("1. 正在添加短信的装饰功能")
def message_pay():
print("----正在做短信安全验证-----")
func()
return message_pay
def gesture(func):
print("2. 正在添加手势的装饰功能")
def gesture_pay():
print("----正在做手势密码安全验证---")
func()
return gesture_pay
# 短信安全验证 和 手势密码安全验证
@gesture # pay = gesture(pay)
@message # pay = message(pay)
def pay():
print("---正在支付中---")
# 装饰器装饰函数是自动执行
pay()
打印结果:
1. 正在添加短信的装饰功能
2. 正在添加手势的装饰功能
----正在做手势密码安全验证---
----正在做短信安全验证-----
---正在支付中---
- 装饰的顺序:从内到外,就和我们平时穿衣服一样
- 执行的顺序:从外到内,就和我们平时脱衣服一样
类装饰器
# 类对象可以的当做 装饰器函数
class Person(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
# print("我被调用啦哟")
self.func()
# 类装饰器
@Person # t = Person(t) 将函数当做参数传递 __init__() takes 1 positional argument but 2 were given
def t():
print("我是被装饰函数....")
# 'Person' object is not callable
# 调用对象 需要实现 __call__方法
t()
类装饰器平时用的较少,了解即可
带参数的装饰器
def set_level(level):
def set_fun(func):
def call_func():
if level == 1:
print("正在做手势安全验证.....")
elif level == 2:
print("正在做短信验证码验证.....")
func()
return call_func
return set_fun
@set_level(1) # 1. 调用函数 set_level(1), 返回set_fun, 2. 让set_fun作为装饰器函数 login = set_fun(login)
def login():
print("-----正在登陆中-------")
@set_level(2)
def pay():
print("-----正在支付中-------")
login()
pay()
带参数的装饰器可以这样理解:,最外层将相当于一般的函数,只是将参数给里面的装饰器,实际上里面的闭包才是装饰器.
私信我获取 python 全套资料