搞懂Python装饰器(decorator)

本文详细探讨了Python装饰器的工作原理,包括闭包的应用、如何避免代码重复,以及使用functools.wraps实现函数名保留。通过实例演示了装饰器的嵌套和执行顺序,帮助读者理解装饰器在函数增强中的作用。
部署运行你感兴趣的模型镜像

之前一直在用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)

这里的wrapper函数和其所在的上下文构成了闭包
关于闭包可以参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
https://www.zhihu.com/question/34210214
这里的@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 year(*args, **kw):
            print('wrapper:', func.__name__)
            return func(*args, **kw)
        return year
    return decorator

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

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

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

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

def second(now):
    def decorator(func):
        print('decorator:', func.__name__)
        def second(*args, **kw):
            print('wrapper:', func.__name__)
            return func(*args, **kw)
        return second
    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: second
decorator: minute
decorator: hour
decorator: day
decorator: month
wrapper: month
wrapper: day
wrapper: hour
wrapper: minute
wrapper: second
wrapper: print_time
2021-07-26 09:24:00.976288

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

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)))))))
因而就有了上面的结果。

参考:https://zhuanlan.zhihu.com/p/45458873

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值