废话不说。。。
首先,装饰器是干什么的,自然是装饰用的。
这是很多课程里面都会讲的一句话,这句话理解的人自然懂,但是我们这种小白,就云里雾里了。
其实说白了就是给函数在不更改源代码及其调用方式的情况下,增加函数的一些功能。
那装饰器到底是个啥?
装饰器就是个不一样的函数。
那么在理解装饰器是啥函数之前,我们要先看懂三句话。
1、函数就是变量
这一点在用了很长时间Python人上都不一定会知道,其实函数是个object类型的变量,那么自然也是可以赋值等操作的,也自然可以当做参数进行传递。
2、高阶函数
何为高阶函数? 就是以函数当做变量,或者返回值是函数的函数。
3、嵌套函数
就是函数里面定义函数
懂了这三个东西了之后,其实
装饰器 = 高阶函数 + 嵌套函数
干讲,大家也是不会太仔细去看的,那我们举个栗子。
import time
def f():
print('嘤嘤嘤')
time.sleep(2)
f()
这样我们就定义好了一个超简单的函数,就是输出一句话然后在有个延迟。
那么比如说我们现在有了一个需求:要测试函数运行的时间。
那么普通方法也是可以实现的。
def new_f():
start_time = time.time()
f()
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
new_f()
这样定义一个新函数是完全可以的,但如果你有10个函数呢,或者成百上千上万个呢?定义1万个新函数?
显然这是不可能的。
那么装饰器就是这样一个作用。
import time
def time_counter(func):
start_time = time.time()
func()
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
def f():
print('嘤嘤嘤')
time.sleep(2)
time_counter(f) # f是不带括号的,才能作为一个object类型变量
这样我们已经写了半个装饰器了,它与之前的不同,就是我们可以传入函数,使它能够稍微泛化一点。
那么我们之前说了,装饰器是 高阶函数 + 嵌套函数,那么我们想,在time_counter这个函数里,是不是那段计算时间的功能可以封装在一个函数里呢?自然可以。
import time
def time_counter(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
return wrapper
def f():
print('嘤嘤嘤')
time.sleep(2)
f = time_counter(f) # 将time_counter(f)的返回结果返回给f, 实际上就是wrapper --> f
f()
好了,那么这个装饰器,我们就搞完了,但是大家会发现,在最后重新赋值的方法好像不太顶啊。。
这么搞,一样是很恶心人的(;´д`)ゞ。这个时候就要用到我们Python自有的一个功能:语法糖
import time
def time_counter(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
return wrapper
@time_counter # 就相当于是f = time_counter(f)
def f():
print('嘤嘤嘤')
time.sleep(2)
f()
那么简单的装饰器我们就学会了,但是,如果我们有好几个需求呢?
多重装饰
比如说,现在,产品经理,emmm又来了,说,你这个,啊,我想输出时间,我又想让它输出日志,你再改改,先输出日志,再输出时间。
(╯°Д°)╯︵┻━┻ (不,我不想)
import time
def log(func):
def wrapper(*args, **kwargs):
print("Log: Begin call", func.__name__)
temp = func(*args, **kwargs)
return temp
return wrapper
def time_counter(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
return wrapper
@log
@time_counter
def f():
print('嘤嘤嘤')
time.sleep(2)
f()
那么其实就是再搞另一个装饰器,二次包装,从下而上进行包装,这个可以想象成是包装纸,最外面的也就是你最先看到的包装纸。
那我们在想想 如果函数有返回值的时候,会出现什么状况。
import time
def time_counter(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
return wrapper
@time_counter
def f():
time.sleep(2)
return "嘤嘤嘤"
print("result: ", f())
Consuming time: 2.0001s
result: None
这时候大家会发现,诶嘿,咋输出不太对劲呢。
这一块,大家注意,这时候输出的不是f()的返回值了,而是wrapper的,所以要让wrapper返回才可以。
import time
def time_counter(func):
def wrapper():
start_time = time.time()
temp = func()
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
return temp
return wrapper
@time_counter
def f():
time.sleep(2)
return "嘤嘤嘤"
print(f())
那如果f()有参数呢?
import time
def time_counter(func):
def wrapper():
start_time = time.time()
temp = func()
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
return temp
return wrapper
@time_counter
def f(x):
time.sleep(2)
return x
print(f('嘤嘤嘤'))
这时候会报错
TypeError: wrapper() takes 0 positional arguments but 1 was given
诶嘿,又出问题了,这是因为 f 我们要传进去参数,但是wrapper没有参数。
所以切记,下面有参数的时候,上面wrapper也需要修改,wrapper和 f 传的参数不必一样。
import time
def time_counter(func):
def wrapper(a): # 在这个地方
start_time = time.time()
temp = func(a) # 还有这个地方,两个地方,需要将参数传进去
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
return temp
return wrapper
@time_counter
def f(x):
time.sleep(2)
return x
print(f('嘤嘤嘤'))
那。。我们有两个参数呢。。。
同样的wrapper需要给它两个参数,在原先的两个地方加进去就可以了,包括*args和**kwargs也是一样的,不管有几个参数,统一这样写,是绝对没问题的。
import time
def time_counter(func):
def wrapper(*args, **kwargs):
start_time = time.time()
temp = func(*args, **kwargs)
end_time = time.time()
print("Consuming time: {:.4f}s".format(end_time - start_time))
return temp
return wrapper
@time_counter
def f(x, y):
time.sleep(2)
return x
print(f('嘤嘤嘤', 1))
那么我们在来看,如果说,产品经理又来了。。。。
他说,这个日志,他想自定义。。他想动态显示。。
那我们再改。。
def log(text): # 传入一个文本数据
def decorator(func): # 然后嵌套一个装饰器
def wrapper(*args, **kwargs):
print(text)
temp = func(*args, **kwargs)
return temp
return wrapper
return decorator
@log("日志") # 这个很关键,我们需要输入内容了,就是text的内容
def f(x, y):
return x
print(f("嘤嘤嘤", 1))
嵌套里面再嵌套,这时候只要去修改“日志”这个里面的内容就可以,改变其日志显示了。
那我们的装饰器就写完了,有什么不会的话,欢迎评论或私聊。(`・ω・´)