图解装饰器以及常用装饰器

一、什么是装饰器

这个名词定义是这样的:
装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会执行被装饰的函数并返回,或者将其替换成另一个函数。
在这里插入图片描述

二、装饰器返回的两种情况示例

2.1 f中有调用g(x)

def decorate(func):
    def f(*args, **kwargs):
        res = func(*args, **kwargs) * 2
        print('Execuet f: g(x)*2')
        return res
    return f

@decorate
def g(x):
    print('Execuet g(x)')
    return x**2

"""
>>> g(5)
Execuet g(x)
Execuet f: g(x)*2
50
"""

很显然,我们看到g(x)是被嵌入在f中执行的。

2.2 f中没有调用g(x)

def decorate(func):
    def f(*args, **kwargs):
        print('Execuet f')
    return f


@decorate
def g(x):
    print('Execuet g(x)')
    return x**2

"""
>>> g(5)
Execuet f
"""

三、装饰器为什么叫装饰器

   显然装饰器的装饰两个字,应该会是有些文章的。不然就这样吧g(x)嵌入到f中,好像看着也没啥花头。所以,我们借用他这框架,来实现一下对g(x)的装饰。

  • 增加一些文字装饰
def decorate(func):
    def f(*args, **kwargs):
        if len(args) == 1:
            args_str = str(args)
        else:
            args_str = ','.join(args)
        print(f'好了,接下来我们要执行g({args_str})啦')
        res = func(*args, **kwargs)
        print(f'好了,g({args_str})执行好了,结果是: {res}。')
        return res
    return f


@decorate
def g(x):
    print('Execuet g(x)')
    return x**2

"""
>>> g(5)
好了,接下来我们要执行g((5,))啦
Execuet g(x)
好了,g((5,))执行好了,结果是: 25。
25
"""

   很显然,上面的装饰只是个小小示例,没啥应用价值。在我们进行特征工程,或者一些自己写的封装任务中,我们都比较关注该任务的执行时间。
   所以,其实我们是可以对一些必要的函数做装饰,计算该函数的运行时间,而不是在每个函数里都重复的写计算该函数运行时间的小脚本。

3.1 实用的clock装饰器

from functools import wraps
import time
def clock(func):
    # 把func的相关属性复制到clocked
    @wraps(func) 
    def clocked(*args, **kwargs):
        start = time.perf_counter()
        res = func(*args, **kwargs)
        cost_seconds = time.perf_counter() - start
        msg = f'func: {func.__name__}, cost: {cost_seconds:.5f}'
        print(msg)
        return res
    return clocked

from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
iris = load_iris()

@clock
def scale_data(df):
    scale = StandardScaler()
    return scale.fit_transform(df)

iris_dt_scale = scale_data(iris.data)

"""
>>> iris_dt_scale = scale_data(iris.data)
func: scale_data, cost: 0.00044
"""

四、标准库中的装饰器

4.1 使用functools.lru_cache做备忘

   lru_cache (Least Recently Used cached)实现了备忘的功能。是一个优化技术,它把耗时的函数的结果保存起来,避免闯入相同的参数时重复计算。递归优化lru_cache(maxsize=128, typed=False)

注:
为了得到最佳性能应该用2的幂次 做 maxsize
typed =True 不同类型不同管理 如(1, 1.0)

lru_cache 斐波那契数优化

  • 未加lru_cache,fib(6) 递归

from functools import wraps
import time

def clock(func):
    @wraps(func) 
    def clocked(*args, **kwargs):
        args_str = ','.join([repr(arg) for arg in args])
        start = time.perf_counter()
        res = func(*args, **kwargs)
        cost_seconds = time.perf_counter() - start
        msg = f'func: {func.__name__}({args_str}), cost: {cost_seconds:.5f}'
        print(msg)
        return res
    return clocked

@clock
def fib(n):
    if n < 2:
        return n
    return fib(n-2) + fib(n - 1)

fib(6)
"""
>>> fib(6)
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00055
func: fib(1), cost: 0.00000
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00031
func: fib(3), cost: 0.00525
func: fib(4), cost: 0.00626
func: fib(1), cost: 0.00000
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00174
func: fib(3), cost: 0.00224
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00040
func: fib(1), cost: 0.00000
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00031
func: fib(3), cost: 0.00064
func: fib(4), cost: 0.00137
func: fib(5), cost: 0.00414
func: fib(6), cost: 0.01092
8
"""

可以看到,递归的时候很多都进行了重复运算

  • 加入lru_cache,fib(6) 递归
from functools import wraps, lru_cache

@clock
@lru_cache(maxsize=128, typed=False)
def fib(n):
    if n < 2:
        return n
    return fib(n-2) + fib(n - 1)

"""
>>> fib(6)
func: fib(0), cost: 0.00000
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00232
func: fib(1), cost: 0.00000
func: fib(2), cost: 0.00000
func: fib(3), cost: 0.00051
func: fib(4), cost: 0.00340
func: fib(3), cost: 0.00000
func: fib(4), cost: 0.00000
func: fib(5), cost: 0.00036
func: fib(6), cost: 0.00418
8
"""

4.2 使用functools.singledispatch做泛函数

   泛函数,顾名思义,就是泛化能力很强,很广泛的意思。但是,我们都知道,一些函数只能进入特定的参数,进入其他类型的参数会报错。
   比如 g(x): x**2这个函数,显然str类型的参数是无法传入的。但是,我们有想要实现这个参数的传入,或者说是不同参数传入,我对这个参数的处理会有略微的不同,这个时候如果用try-except写起来会比较繁琐。
   所以,我们可以引入singledispatch,对我们要泛化的函数进行修饰。然后用 函数.register(str) 去写str参数进入函数的时候的处理。

  • 我们来看下一个简单的实例。当g(x): x**2这个函数进入参数是str时,返回x*len(x)**2
from functools import singledispatch

@singledispatch
def g(x):
    return x**2

@g.register(str)
def _(x):
    return x*len(x)**2

"""
>>> g('aa'), g(2)
('aaaaaaaa', 4)
"""

参考

《Fluent Python》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Scc_hy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值