前言:在学习装饰器前,我们先来了解两个函数概念
函数中定义函数
def print_third(word):
def repeat(times):
return word * times
print(repeat(3))
print_third('xiaohan')
# output:xiaohanxiaohanxiaohan
函数返回函数
def print_words(word):
def repeat(times):
return word * times
return repeat
print(print_words('xiaohan')(3))
f = print_words('xiaohan') # f是一个函数对象(具体来说是repeat)
print(f(5)) # f(5)也就是repeat(5)
# output:xiaohanxiaohanxiaohan
# xiaohanxiaohanxiaohanxiaohanxiaohan
%1
def say_hello():
print('Hello!')
say_hello() # 每次调用会输出一次Hello!
# output:Hello!
%2
# 如果我们想让它输出时同时附带上此时输出的时间
say_hello() # 调用函数
# output:[ 2021-02-08 18:58:54.888791 ]
# Hello!
%3
# 假设我们已经有了一个能满足该需求的装饰器 @time
# 只要像这样来装饰 say_hello() 即可
@time
def say_hello():
print('Hello!')
say_hello() # 调用函数
# output:[ 2021-02-08 18:58:54.888791 ]
# Hello!
import datetime # 日期时间相关库、用于后续获取当前时间
def time(func):
def wrapper(*args, **kw):
print('[', datetime.datetime.now(), ']')
# 用来输出当前时间
return func(*args, **kw)
# 用来实现被装饰函数
return wrapper
@time
def say_hello():
print('Hello!')
say_hello() # 调用函数
# output:[ 2021-02-08 18:58:54.888791 ]
# Hello!
%1
@time
def say_hello():
print('Hello!')
%2
def say_hello():
print('Hello!')
say_hello = time(say_hello)
# time(say_hello) 实质上是 wrapper
# 也就是说say_hello() 其实变成了 time() 中的 wrapper()
%3
# 在之前代码当中加入一行
print(say_hello)
# output:.wrapper at 0x0000017B91DB0B70>
# 可以观察到 say_hello() 其实变成了 time() 中的 wrapper()
%1
# 当一个函数不被装饰器装饰时、其函数名称就是自己
# 1. 在编译器中直接输入 print(say_hello)、显示其为 function say_hello
# 2. 使用 print(say_hello.__name__) 可以直接获取到其函数名称
def say_hello():
print('Hello!')
print(say_hello)
print(say_hello.__name__)
# output:
# say_hello
%2
# 如果我们用装饰器 @time 来修饰这个函数
import datetime # 日期时间相关库、用于后续获取当前时间
def time(func):
def wrapper(*args, **kw):
print('[', datetime.datetime.now(), ']')
# 用来输出当前时间
return func(*args, **kw)
# 用来实现被装饰函数
return wrapper
@time
def say_hello():
print('Hello!')
print(say_hello)
print(say_hello.__name__)
# output:.wrapper at 0x000001B9568F0B70>
# wrapper
# 可以看到其名字信息被装饰器中的函数 wrapper 覆盖了
# 由于装饰器本质上是用一个新的函数来替换被装饰的函数、所以函数的元信息会被覆盖
%4
# 保留被装饰函数的元信息
import datetime
import functools
def time(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('[', datetime.datetime.now(), ']')
return func(*args, **kw)
return wrapper
@time
def say_hello():
print('Hello!')
print(say_hello)
print(say_hello.__name__)
# output:
# say_hello
%1
import datetime
import functools
def time(format):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(datetime.datetime.now().strftime(format))
return func(*args, **kw)
return wrapper
return decorator
# wrapper() 中原本的 print('[', datetime.datetime.now(), ']') 被修改为 print(datetime.datetime.now().strftime(format))
# 其中的 format 便是装饰器的参数、也就是时间格式
%2
# 语法
# 使用时,在装饰器 @time 后添加括号并写上参数
@time('%Y/%m/%d %H:%M:%S')
def say_hello():
print('Hello!')
say_hello()
# output:2021/02/08 19:22:32
# Hello!
%3
# '%Y/%m/%d %H:%M:%S' 是 datetime 包中用于指定时间格式的字符串
# %Y 表示年 %m 表示月 %d 表示天 %H 表示小时 %M 表示分钟 %S 表示秒
%1
# 带参数的装饰器的实现为什么要三层函数嵌套
@time('%Y/%m/%d %H:%M:%S')
def say_hello():
print('Hello!')
%2
def say_hello():
print('Hello!')
say_hello = time('%Y/%m/%d %H:%M:%S')(say_hello)
#
# 而不带参数的装饰器的等效代码是 say_hello = time(say_hello)
# 对比可以看出、带参数的装饰器的等效代码多了一次函数调用
# 通过这种方式将装饰器参数传递到内部的两层函数中、这之后便回到了不带参数的装饰器的情形