搞懂Python装饰器(decorator)

之前一直在用Python的装饰器,比如著名的5行代码

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "Hello, World!"

但没有对其实现方式过多探究,今天闲来无事分析下装饰器

装饰器顾名思义,就是对函数进行装饰的一个语法糖,其作用就是在不给函数添加额外代码的情况下给函数添加功能
比如我们想打印当前时间:

import datetime

now = datetime.datetime.now()

def print_time(now):
	print(now)

print_time(now)

假设我们希望给print_time函数添加打印年月日时分秒的功能,我们可以这样写:

import datetime

now = datetime.datetime.now()

def print_time(now):
    print(now.year)
    print(now.month)
    print(now.day)
    print(now.hour)
    print(now.minute)
    print(now.second)
    print(now)

print_time(now)

这样写有两个问题:

  1. 破坏了print_time的定义
  2. 如果存在成百上千个类似print_time的函数,复制粘贴可能都要累死

为了解决上面的问题Python给了语法糖decrator,所以上面的代码可以改写成:

import datetime

now = datetime.datetime.now()

def split(now):
    def decorator(func):
        def wrapper(*args, **kw):
            print(now.year)
            print(now.month)
            print(now.day)
            print(now.hour)
            print(now.minute)
            print(now.second)
            return func(*args, **kw)
        return wrapper
    return decorator

@split(now)
def print_time(now):
    print(now)

print_time(now)

分析一下,这里的@split(now)相当于执行了

print_time = split(now)(print_time)

前面的print_time为变量名,后面的print_time为函数名,也就是说此时print_time变量名指向了split(now)(print_time)的返回对象即wrapper函数(注意,split(now)返回了decorator函数,所以split(now)(print_time)即调用了decrator函数,返回wrapper函数)
我们可以打印一下此时的print_time的__name__属性

>>> print(print_time.__name__)
wrapper

但有的时候我们需要__name__属性为被装饰的函数名,而不是装饰器中函数的函数名,可能我们需要在wrapper函数中加上重命名的语句

wrapper.__name__ = func.__name__

但我们有更好的方式:

import functools
import datetime

now = datetime.datetime.now()

def split(now):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print(now.year)
            print(now.month)
            print(now.day)
            print(now.hour)
            print(now.minute)
            print(now.second)
            return func(*args, **kw)
        return wrapper
    return decorator

@split(now)
def print_time(now):
    print(now)

print_time(now)

上面例子中由于装饰器中存在参数now,所以需要三层嵌套装饰器,如果没有参数,则只需要两层即可

注意wrapper中的*args, **kw,这种写法使得该函数能够接受任意形式参数的调用

一个函数可以被多个装饰器装饰

import functools
import datetime

now = datetime.datetime.now()

def year(now):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print(now.year)
            return func(*args, **kw)
        return wrapper
    return decorator

def month(now):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print(now.month)
            return func(*args, **kw)
        return wrapper
    return decorator

def day(now):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print(now.day)
            return func(*args, **kw)
        return wrapper
    return decorator

def hour(now):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print(now.hour)
            return func(*args, **kw)
        return wrapper
    return decorator

def minute(now):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print(now.minute)
            return func(*args, **kw)
        return wrapper
    return decorator

def second(now):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print(now.second)
            return func(*args, **kw)
        return wrapper
    return decorator

@year(now)
@month(now)
@day(now)
@hour(now)
@minute(now)
@second(now)
def print_time(now):
    print(now)

print_time(now)

可以发现结果一样。但可能你会对上面装饰器的执行顺序有些迷惑,没关系,我们下面分析一下
首先,我们知道这几个装饰器就相当于

def print_time(now):
    print(now)
print_time = year(now)(month(now)(day(now)(hour(now)(minute(now)(second(now)(print_time))))))

参考:https://docs.python.org/3/reference/compound_stmts.html#function

此时print_time指向的是最外层函数year中decorator返回的wrapper1函数;wrapper1的参数为month中decrator返回的wrapper2函数;wrapper2的参数为day中decrator返回的wrapper3函数;wrapper3的参数为hour中decrator返回的wrapper4函数;wrapper4的参数为minute中decrator返回的wrapper5函数;wrapper5的参数为second中decrator返回的wrapper6函数;而wrapper6的参数即为print_time函数
有没有感觉很像递归
所以一下代码的执行结果就是

import functools
import datetime

now = datetime.datetime.now()

def year(now):
    def decorator(func):
        print('decorator:', func.__name__)
        def wrapper1(*args, **kw):
            print('wrapper:', func.__name__)
            return func(*args, **kw)
        return wrapper1
    return decorator

def month(now):
    def decorator(func):
        print('decorator:', func.__name__)
        def wrapper2(*args, **kw):
            print('wrapper:', func.__name__)
            return func(*args, **kw)
        return wrapper2
    return decorator

def day(now):
    def decorator(func):
        print('decorator:', func.__name__)
        def wrapper3(*args, **kw):
            print('wrapper:', func.__name__)
            return func(*args, **kw)
        return wrapper3
    return decorator

def hour(now):
    def decorator(func):
        print('decorator:', func.__name__)
        def wrapper4(*args, **kw):
            print('wrapper:', func.__name__)
            return func(*args, **kw)
        return wrapper4
    return decorator

def minute(now):
    def decorator(func):
        print('decorator:', func.__name__)
        def wrapper5(*args, **kw):
            print('wrapper:', func.__name__)
            return func(*args, **kw)
        return wrapper5
    return decorator

def second(now):
    def decorator(func):
        print('decorator:', func.__name__)
        def wrapper6(*args, **kw):
            print('wrapper:', func.__name__)
            return func(*args, **kw)
        return wrapper6
    return decorator

@year(now)
@month(now)
@day(now)
@hour(now)
@minute(now)
@second(now)
def print_time(now):
    print(now)

print_time(now)
decorator: print_time
decorator: wrapper6
decorator: wrapper5
decorator: wrapper4
decorator: wrapper3
decorator: wrapper2
wrapper: wrapper2
wrapper: wrapper3
wrapper: wrapper4
wrapper: wrapper5
wrapper: wrapper6
wrapper: print_time
2021-01-14 23:04:27.213411

把上面的装饰器改写为如下的嵌套函数的形式,便于理解

print_time = year(now)(month(now)(day(now)(hour(now)(minute(now)(second(now)(print_time))))))
print_time(now)

我们姑且称这两行语句分别为构建和调用,构建过程可以理解为简单的函数嵌套执行顺序,是从内而外执行;
由于每一层返回的都是其子层的函数,所以调用顺序可以简写为如下:
print_time(wrapper6(wrapper5(wrapper4(wrapper3(wrapper2(wrapper1(now)))))))
因而就有了上面的结果。

你可能感兴趣的:(Python,python,装饰器,decorator,语法糖,递归)