Python高效编程实战---8、装饰器使用技巧进阶

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qd_ltf/article/details/79703920

一、使用装饰器函数

def memo(func):
    cache = {}

    def wrap(*args, **kwargs):
        if args not in cache:
            cache[args] = func(*args, **kwargs)
            print func.__name__  # fibonacci
        return cache[args]

    return wrap

@memo  # 等同于   fibonacci=memo(fibonacci) 
def fibonacci(n):
    if n <= 1:
        return 1
    print fibonacci.__name__  # wrap 调用自己,而调用 fibonacci=memo(fibonacci),因为变量以最后赋值的为准
    return fibonacci(n - 1) + fibonacci(n - 2)

print fibonacci(20)

二、如何为被装饰的函数保存元数据

在函数对象中保存着一些函数的元数据,例如:

f.name :函数的名字
f.doc :函数文档字符串
f.moudle :函数所属模块名
f.dict :属性字典
f.defaults :默认参数元组
…..

在使用装饰器后,再使用上面这些属性访问时,看到的是内部包裹函数的元数据,原来函数的元数据便丢失掉了,我们可以使用标准库functools中装饰器wraps装饰内部包裹函数,可以制定原函数的某些属性,更新到包裹函数上面

from functools import wraps
def memo(func):
    @wraps(func)
    def wrap(*args, **kwargs):
        func(*args, **kwargs)
    return wrap

三、定义带参数的装饰器

案例:
实现一个装饰器,它用来检查被装饰函数的参数类型。装饰器可以通过参数指明函数参数的类型,调用时如果检测出来类型不匹配则抛出异常

@typeassert(str,int,int)
def (a,b,c)
@typeassert(y=list)
def g(x,y):

解决方案(python3):
带参数的装饰器,也就是根据参数定制化一个装饰器。可以看成生产装饰器的工作,每次调用typeassert,返回一个特定的装饰器,然后用它去修饰其它函数。需要用到提取签名函数inspect.signature()

from inspect import signature

def typeassert(*ty_args, **ty_kargs):
    def decorator(func):
        sig = signature(func)
        print(sig)
        btypes = sig.bind_partial(*ty_args, **ty_kargs).arguments
        print(btypes)

        def wrap(*args, **kwargs):
            wbtypes = sig.bind_partial(*args, **kwargs).arguments
            for name, obj in wbtypes.items():
                if name in btypes:
                    if not isinstance(obj, btypes[name]):
                        raise TypeError('"%s" must be %s' % (name, btypes[name]))
            return func(*args, **kwargs)

        return wrap

    return decorator

@typeassert(int, c=str)
def func(a, b, c):
    print(a, b, c)

@typeassert(int, int)
def f2(x, y):
    return x * y

func(1, 2, 3)  # 报错

四、实现属性可修改的函数装饰器

# (python3)
from random import randint
from time import sleep,time
import logging

def warn(timeout):
    def decorator(func):
        def wraper(*args, **kargs):
            start =time()
            res = func(*args, **kargs)
            used=time()-start  # 统计耗时
            if used>timeout:
                msg='"%s:" %s >%s'%(func.__name__,used,timeout)   # 日志信息
                logging.warn(msg)   # 显示日志
            return res

        def setTimeout(n):
            nonlocal timeout
            timeout = n
        wraper.setTimeout = setTimeout  # 将setTimeout()设置成wraper的属性,在wraper.__dict__内可查找到
        return wraper
    return decorator

@warn(1.5)
def func():
    print('in func')
    while randint(0, 1):
        sleep(0.5)
func.setTimeout(1)  # 修改timeout值
for _ in range(30):
    func()

五、如何在类中定义装饰器

案例:
1. 实现一个能将函数调用信息记录到日志的装饰器
2. 把每次函数的调用时间,执行时间,调用次数写入日志
3. 可以对被装饰函数分组,调用信息记录到不同日志
4. 动态修改参数,比如日志格式
5. 动态打开关闭日志输出功能

解决方案:
为了让装饰器在使用上更加灵活,可以把类的实例方法作为装饰器,此时在包裹函数中就可以持有实例对象,便于修改属性和拓展功能
补充知识:日志
日志主要包含三大部分:http://python.usyiyi.cn/translate/python_278/howto/logging.html
○ 记录器:配置记录器,并用来生成日志消息
○ 处理器:来来处理日志消息,如存放到磁盘文件、标准输出等
○ 格式化器:对处理的消息定制样式

import logging
from time import time, localtime, sleep, strftime
from random import choice

class CallingInfo(object):
    def __init__(self, name):
        log = logging.getLogger(name)   # 返回一个记录器logger类的实例
        log.setLevel(logging.INFO)      # 设置记录器的级别
        fh = logging.FileHandler(name + '.log',mode='w')  # 返回一个处理器FileHandler,并将处理结果添加到文件中
        log.addHandler(fh)    # 绑定记录器与处理器的
        self.log = log        # 记录器作为实例的一个属性
        log.info('Start'.center(50,'-'))   # 产生第一条消息
        self.formatter = '%(func)s - %(ltime)s - %(used)s - %(ncalls)s'  # 用字典的方法形成信息样式,便于用类的方法修改样式

    def decorator(self, func):
        def wrap(*args, **kwargs):
            wrap.ncalls += 1
            ltime = localtime()  # 返回的秒数
            start = time()
            res = func(*args, **kwargs)  # 调用被装饰的函数
            used = time() - start
            f = {}
            f['func'] = func.__name__
            f['ltime'] = strftime('%x %X', ltime)  # 返回时间格式
            f['used'] = used
            f['ncalls'] = wrap.ncalls
            msg = self.formatter % f   # 将字典的值填充到样式中
            self.log.info(msg)  # 将msg传入记录器
            return res
        wrap.ncalls = 0   # 定义ncalls作为wrap的一个属性
        return wrap

    def setFormat(self,format):  # 修改样式
        self.formatter=format   

    def setTurnOn(self):   # 调整记录器级别
        self.log.setLevel(logging.INFO)

    def setTurnOff(self):  # 调整记录器级别
        self.log.setLevel(logging.WARN)

log1 = CallingInfo('log1')
log2 = CallingInfo('log2')
log2.setTurnOff()
log1.setFormat('%(func)s - %(ltime)s ---- %(ncalls)s')

@log1.decorator
def f():
    print 'in f'

@log1.decorator
def g():
    print 'in g'

@log2.decorator
def h():
    print 'in h'

for _ in xrange(10):
    choice([f, g, h])()   # choice从列中随机选择一个
    sleep(choice([0.5, 1, 1.5]))

猜你喜欢

转载自blog.csdn.net/qd_ltf/article/details/79703920