python 装饰器详解

本贴最后更新于 1395 天前,其中的信息可能已经事过景迁

装饰器

为什么要学

装饰器使用了一种非常巧妙和高级的编程思想。它可以降低代码的耦合度,大大提升了工作效率。在很多的框架中得到了广泛的应用。

学习目标:

  1. 理解装饰器的原理
  2. 掌握装饰器的编写和应用

应用场景

首先我们提出一个问题:

在我们的项目中有一个支付功能的函数pay,假设它的定义如下:

import time
def pay(money):
    print('开始支付{}!'.format(money))
    time.sleep(2)  # 模拟支付大概需要2秒钟
    print('支付成功!')

一天早上正当你在喝咖啡想着下班去哪里浪的时候,产品经理过来提了一个需求,

说最近用户反映支付比较慢,要求统计一下支付的时间。

你想出如下解决方案:

  1. 修改pay函数源码
  2. 写一个记时函数,在pay函数调用前调用后执行

项目中有100多处都调用了pay函数,所以第2种方案肯定要pass。

实际情况下pay函数可能有几十行代码和复杂的逻辑,万一在修改源码的过程中出错,会造成重大事故。

即使当你心惊胆跳的修改好了代码刚上线,产品经理又过来说还要在支付前给用户一个一经付款不能退款的提示。

。。。。
所以上面两个方案都不可行。

我们需要一种能够在不改变函数源码和函数调用方式的情况下给函数增加新的功能。

这个就是装饰器实现的功能,要学习装饰器,我们需要学习几个概念。

函数名即变量

我们看下面的案例:

def f1():
    print('f1函数被调用')

print(f1)
a = f1
a()

<function f1 at 0x0000025FD4E7E940>
f1函数被调用
通过赋值语句,a这个变量指向了函数f1,a再加上括号就调用了这个函数。

image.png

嵌套函数

在函数里面定义了函数的函数就称为嵌套函数。
我们看下面的案例:

def func():
    print('外层函数被调用')
  
    def inner():
        print('内层函数被调用')
  
    inner()
func()

外层函数被调用
内层函数被调用
我们调用函数func,func函数内部又会调用inner函数。那么我们会不会又需求让函数外部调用inner函数呢?

既然函数名是变量,我们想要调用内层函数,就以使用返回值的方式将函数返回,来达到这个结果。

我们看下面的案例:

def func():
    print('外层函数被调用')
  
    def inner():
        print('内层函数被调用')
    print(inner)
    return inner
res = func()
print(res)

外层函数被调用
<function func..inner at 0x0000025FD4E7E430>
<function func..inner at 0x0000025FD4E7E430>

res()

内层函数被调用
image.png

高阶函数

接收函数作为参数,或者返回值是函数的函数称为高阶函数。

单层装饰器

有了这些概念,我们就可以解决上面的问题。

我们看下面的代码:

def get_time(f):
  
    def inner(*arg,**kwarg):
        s_time = time.time()
        res = f(*arg,**kwarg)
        e_time = time.time()
        print('耗时:{}秒'.format(e_time - s_time))
        return res
    return inner

def pay(money):
    print('开始支付{}!'.format(money))
    time.sleep(2)  # 模拟支付大概需要2秒钟
    print('支付成功!')

pay = get_time(pay)
pay(500)

开始支付500!
支付成功!
耗时:2.0047152042388916秒
image.png

上面的案例感觉还是修改了函数的调用方式,python中用特殊的语法糖,实现和上面一样的效果,代码如下:

def get_time(f):
  
    def inner(*arg,**kwarg):
        s_time = time.time()
        res = f(*arg,**kwarg)
        e_time = time.time()
        print('耗时:{}秒'.format(e_time - s_time))
        return res
    return inner

@get_time
def pay(money):
    print('开始支付{}!'.format(money))
    time.sleep(2)  # 模拟支付大概需要2秒钟
    print('支付成功!')

# pay = get_time(pay)
pay(500)

开始支付500!
支付成功!
耗时:2.0009818077087402秒
装饰器是一个装饰函数的函数,能够在不改变函数源码和函数调用方式的情况下给函数增加新的功能

高阶装饰器

产品经理要求在支付前提示“虚拟产品暂不支持退款”

def tip(f):
    def inner(*arg,**kwarg):
        print('虚拟产品暂不支持退款')
        res = f(*arg,**kwarg)
        return res
    return inner

@tip
def pay(money):
    print('开始支付{}!'.format(money))
    time.sleep(2)  # 模拟支付大概需要2秒钟
    print('支付成功!')

def pay1():
    pass

def pay2():
    pass

def pay3():
    pass
pay(500)

虚拟产品暂不支持退款
开始支付500!
支付成功!
产品经理又要求在支付修改提示信息为“请确认是本人支付”

当然可以直接修改装饰器,但考虑到会有很多个函数都需要增加不同的提示信息。

那么采用这种方式就需要给每个函数写装饰器,那也会造成代码冗余,工作量加大。

所以我们需要写一个函数能够动态的输出不同装饰器,这个就是高阶装饰器

def tip(content):
    def decorator(f):
        def inner(*arg,**kwarg):
            print(content)
            res = f(*arg,**kwarg)
            return res
        return inner
    return decorator

@tip('请确认是本人支付')
def pay(money):
    print('开始支付{}!'.format(money))
    time.sleep(2)  # 模拟支付大概需要2秒钟
    print('支付成功!')

pay(500)

@tip('虚拟产品暂不支持退款')
def pay1(money):
    print('开始支付{}!'.format(money))
    time.sleep(2)  # 模拟支付大概需要2秒钟
    print('支付成功!')
  
pay1(1000)

请确认是本人支付
开始支付500!
支付成功!
虚拟产品暂不支持退款
开始支付1000!
支付成功!

多个装饰器修饰

有时候会在一个函数上应用多个装饰器

def get_time(f):
  
    def inner(*arg,**kwarg):
        s_time = time.time()
        res = f(*arg,**kwarg)
        e_time = time.time()
        print('耗时:{}秒'.format(e_time - s_time))
        return res
    return inner

def tip(content):
    def decorator(f):
        def inner(*arg,**kwarg):
            print(content)
            res = f(*arg,**kwarg)
            return res
        return inner
    return decorator

@tip('虚拟产品暂不支持退款')
@get_time
def pay(money):
    print('开始支付{}!'.format(money))
    time.sleep(2)  # 模拟支付大概需要2秒钟
    print('支付成功!')

pay(500)

虚拟产品暂不支持退款
开始支付500!
支付成功!
耗时:2.0000970363616943秒
修饰的时候是从里往外修饰,执行的时候,是从外往里执行

  • Python
    105 引用 • 237 回帖 • 2 关注
回帖
请输入回帖内容 ...