Python装饰器详解

(一)不带参数的装饰器


1.1:不使用Python语法糖

from time import sleep as sleeping
from time import time as nowTime
from functools import wraps

# 不带参数的装饰器
# 这个是在传递了sleep函数之后,并没有调用,而是在fn调用的时候,再调用sleep函数。fn函数其实就是返回的wrap函数,因此,调用fn函数就是调用wrap函数,wrap函数在执行时调用的就是之前传递进来的sleep函数。这种装饰是将swap函数返回之后装饰为fn函数,因此装饰器的效果就是通过调用wrap函数去实现原有的sleep函数的功能(睡2秒),并且在sleep函数的功能之上再增加了一个计算sleep函数执行时长的功能,实现了sleep函数的增强功能


def timeit(fn):
    @wraps(fn)  # 保证将wrap返回之后,其私有属性还是之前的fn的私有属性不变
    def wrap(*args,**kwargs):
        start = nowTime()
        ret = fn(*args,**kwargs)
        print(nowTime() - start)
        return ret
    return wrap


def sleep(n):
    sleeping(n)
    print("我睡了{}秒".format(n))
    return n

fn = timeit(sleep)

fn(2)
1.2:使用Python语法糖
from time import sleep as sleeping
from time import time as nowTime
from functools import wraps
def timeit(fn):
    @wraps(fn)  # 保证将wrap返回之后,其私有属性还是之前的fn的私有属性不变
    def wrap(*args,**kwargs):
        start = nowTime()
        ret = fn(*args,**kwargs)
        print(nowTime() - start)
        return ret
    return wrap

# @timeit实现的功能就是将sleep函数作为参数传递到timeit函数中,并且返回封装的wrap函数,重命名为sleep。因此sleep(2)调用的不是def sleep(n)函数,而是装饰器返回的wrap函数
@timeit
def sleep(n):
    sleeping(n)
    print("我睡了{}秒".format(n))
    return n

sleep(2)

(二)带参数的装饰器


2.1:不使用Python语法糖
from functools import wraps
import time

# 这个带参数的装饰器接收一个参数,并且返回一个装饰器
# 这个带参数的装饰器和不带参数的装饰器其实就是多传递了一个参数而已,最后返回一个不带参数的装饰器,而这个不带参数的装饰器,使用了带参数的装饰器传递进来的参数,当这个不带参数的装饰器返回的时候,接下来的过程就是一个不带参数装饰器的调用过程。传递一个功能函数sleep给不带参数的装饰器,然后返回一个wrap封装函数,命名为fn,调用这个fn函数,就会调用sleep函数,而sleep函数的参数就是fn(wrap)函数调用时候的参数。 无论是带参数的装饰器还是不带参数的装饰器最后调用的时候实质上都是调用的wrap函数,执行的都是sleep函数的功能

def timeit(cpu_time=False):
    time_func = time.clock if cpu_time else time.time
    def dec(fn):
        @wraps(fn)
        def wrap(*args,**kwargs):
            start = time_func()
            ret = fn(*args,**kwargs)
            print(time_func() - start)
            return ret
        return wrap
    return dec

def sleep(n):
    time.sleep(n)
    return n

fn = timeit()(sleep)

print(sleep(1))
2.2:使用Python语法糖
# 这里的@timeit()语法糖可以传递参数,True 或 False
def timeit(cpu_time=False):
    time_func = time.clock if cpu_time else time.time
    def dec(fn):
        @wraps(fn)
        def wrap(*args,**kwargs):
            start = time_func()
            ret = fn(*args,**kwargs)
            print(time_func() - start)
            return ret
        return wrap
    return dec


@timeit()
def sleep(n):
    time.sleep(n)
    return n
sleep(1)

(三)装饰器应用场景之缓存


import time
from functools import wraps

# 装饰器增加缓存功能
def cache(instance):
    def dec(fn):
        @wraps(fn)
        def wrap(*args, **kwargs):
            # key => fn_name::params
            pos = ','.join((str(x) for x in args))
            kw = ','.join('{}={}'.format(k, v) for k, v in sorted(kwargs.items()))
            key = '{}::{}::{}'.format(fn.__name__, pos, kw)
            ret = instance.get(key)
            if ret is not None:
                return ret
            ret = fn(*args, **kwargs)
            instance.set(key, ret)
            return ret
        return wrap
    return dec

# 创建字典构造函数,用户缓存K/V键值对
class DictCache:
    def __init__(self):
        self.cache = dict()

    def get(self, key):
        return self.cache.get(key)

    def set(self, key, value):
        self.cache[key] = value

    def __str__(self):
        return str(self.cache)

    def __repr__(self):
        return repr(self.cache)

# 创建缓存对象
cache_instance = DictCache()

# Python语法糖调用装饰器
@cache(cache_instance)
def long_time_fun(x):
    time.sleep(x)
    return x

# 调用wrap函数
long_time_fun(3)

(四)装饰器应用场景之监控


import time
from functools import wraps

#创建装饰器,将K/V键值对存储到instance实例中
def mertic(prefix, instance):
    def timeit(fn):
        @wraps(fn)
        def wrap(*args, **kwargs):
            start = time.time()
            ret = fn(*args, **kwargs)
            key = '{}.{}.{}'.format(prefix, fn.__module__, fn.__name__)
            instance.send(key, time.time()-start)
            return ret
        return wrap
    return timeit
# 导入登入模块
import logging

# 创建登入构造函数
class LoggingMetric:
    def send(self, key, value):
        logging.warning('{} => {}'.format(key, value))

# Python语法糖调用装饰器
@mertic(prefix='magedu', instance=LoggingMetric())
def long_time_fun(x):
    time.sleep(x)
    return x

# 调用wrap函数
long_time_fun(1)

(五)装饰器应用场景之检查函数的参数类型


# 写一个装饰器,对函数的部分或者全部参数做类型检查
from functools import wraps

# 定义装饰器
def typed(*type_args,**type_kwargs):
    def dec(fn):
        @wraps(fn)
        def wrap(*args,**kwargs):
            # 这里的enumerate()方法将元组进行索引和值的组合,并且通过循环取出索引以及对于的值
            # i为索引号 ,t为<class int>,通过args元组取出索引号对应的值,并且判断是否为<class int>类型
            for i,t in enumerate(type_args):
                if not isinstance(args[i],t):
                    print('pos {} argument {} type error'.format(i,args[i]))
                    return None

            # 通过Dict.items()方法,将{'x':<class 'int'>,'y':<class 'int'>}转变为[('x', <class 'int'>), ('y', <class 'int'>)],将字典转变为列表,列表里面是元组,这样可以通过循环取出元组中的数据
            for k,t in type_kwargs.items():
                # isinstance()方法是判断kwargs[k]的值是否是t类型的,如果是就返回True,否则为False,如果为False进入条件语句
                if not isinstance(kwargs[k],t): # kwargs[k] => 1,2 => int
                    print('keyword argument {} => {} type error'.format(k,kwargs[k]))
                    return None
            return fn(*args,**kwargs)
        return wrap
    return dec


# 使用关键字参数传递
@typed(x=int,y=int)
def add(x,y):
    print(x)
    print(y)
    return x+y
print(add(x=1,y=2))

# 使用位置参数传递
@typed(int,int)
def add(x,y):
    print(x)
    print(y)
    return x+y
print(add(1,2))

# 使用位置参数和关键字参数混合传递
@typed(int,y=int)
def add(x,y):
    print(x)
    print(y)
    return x + y
print(add(1,y=2))

(六)装饰器应用场景之中偏函数partial的实现


# 其实就是定义一个装饰器就实现了
from functools import wraps
def partial(fn,*p_args,**p_kwargs):
    @wraps(fn)
    def wrap(*args,**kwargs):
        return fn(*p_args,*args,**p_kwargs,**kwargs)
    return wrap

def add(x,y):
    return x+y
inc = partial(add,x=1)

print(inc(y=2))
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值