Python自学15(入手装饰器)

Python自学15(入手装饰器)_第1张图片
" 说不清的无可奈何,我只好相信有舍有得 "

给凡人添加超能力:入手装饰器

  • 1、装饰器是什么
  • 2、自定义装饰器
  • 3、装饰器原理
  • 4、functools.wraps 装饰器(保留元信息)
  • 5、带参数的装饰器
  • 6、带参数的装饰器原理

前言:在学习装饰器前,我们先来了解两个函数概念

函数中定义函数

  1. 在 Python 中,函数内部是可以嵌套地定义函数的
  2. 内层函数只能在包裹它的外层函数中使用,而不能在外层函数外使用
  3. 比如下列的 repeat() 可以在 print_twice() 中使用
  4. 但是不能在 print_twice() 的外部使用
  5. 另外,内层函数中可以使用外层函数的参数或其它变量
  6. 如上面的参数 word
def print_third(word):
    def repeat(times):
        return word * times

    print(repeat(3))
print_third('xiaohan')
# output:xiaohanxiaohanxiaohan

函数返回函数

  1. 函数可以作为另一个函数的参数
  2. 类似的,函数的返回值也可以是一个函数
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、装饰器是什么

  1. 装饰器用来增强一个现有函数的功能,并且不改变这个函数的调用方式
  2. 这种增强是非侵入式的,也就是说无需直接修改函数内部的代码,而是在函数的外部做文章
%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!

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!')
say_hello() # 调用函数
# output:[ 2021-02-08 18:58:54.888791 ]
#         Hello!

3、装饰器原理

%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()

4、functools.wraps 装饰器(保留元信息)

%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

5、带参数的装饰器

%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 表示秒

6、带参数的装饰器原理

%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)
# 对比可以看出、带参数的装饰器的等效代码多了一次函数调用
# 通过这种方式将装饰器参数传递到内部的两层函数中、这之后便回到了不带参数的装饰器的情形

你可能感兴趣的:(Python自学,python,装饰器,functools.wraps)