Python10000小时计划——装饰器

●在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

●装饰器的意义,大概就是把原来的函数包装起来在传给原函数,这样原函数不用改动就能增加功能。

●@是装饰器的语法糖,在定义的时候使用,防止再一次的赋值

●装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。

●通过装饰器log在调用函数前输出调用函数的名称

二层嵌套

#定义一个能打印日志的decoratoe
def log(func):  #接受一个函数作为参数
    #@functools.wraps(func) 
    def wrapper(*args,**kw):    #前面是list后面是字典
        print('call %s():' % func.__name__)
        return func(*args,**kw)
    return wrapper #返回一个函数

#观察上面的log,因为它是一个decorator,所以接受一个函数作为参数1,并返回一个函数。
#我们要借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
    print('2018/6/6')

#调用函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志

now()

 

测试结果

 

三层嵌套

#如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本
def log(text):
    def decorator(func):
        #@functools.wraps(func) 
        def wrapper(*args,**kw):
            print('%s %s():' % (text,func.__name__))
            return func(*args,**kw)
        return wrapper
    return decorator

#这个三层嵌套的decorator用法如下:
@log('excute') #文本text=‘excute’
def rightnow():
    print('2018/6/6')

#调用函数
rifhtnow()

 

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('execute')(now)

 

测试结果 

 

以上两种decoratror的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,

它有__name__等属性,但你去看经过decorator装饰之后的函数,他们的__name__已经从原来的'now'变成了'wrapper'

print(now.__name__)

 

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的(上述代码中#@functools.wraps(func))

 

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

 

练习题1

请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:

# -*- coding: utf-8 -*-
import time, functools
def metric(fn):
    @functools.wraps(fn)
    def wrapper(*args,**kw):
        start = time.time()
        res = fn(*args, **kw)
        end = time.time()
        print('%s executed in %s ms' % (fn.__name__,(end-start)*1000))
        return res
    return wrapper

# 测试
@metric
def fast(x, y):
    time.sleep(0.0012)
    return x + y;

@metric
def slow(x, y, z):
    time.sleep(0.1234)
    return x * y * z;

f = fast(11, 22)
s = slow(11, 22, 33)
if f != 33:
    print('测试失败!')
elif s != 7986:
    print('测试失败!')

 

练习题2

 请编写一个decorator,能在函数调用的前后打印出'begin call'和'end call'的日志。

import functools
def log(pr):
    @functools.wraps(pr)
    def wrapper(*args,**kw):
        print('begin call')
        c=pr(*args,**kw)  #与return func(*args,**kw)不同的是,这里利用赋值来调用函数
        print('end call')
        return c   #那么这里就只会返回函数的值,而不会再次调用函数了
    return wrapper

    
@log
def now():
    print ('1')
    
now()  #调用函数,别漏了

 

 

import functools
def log(func):
    def wrapper():
        print ('begin call')
        func()
        print ('end call')
    return wrapper


@log
def myage():
    print('24')
    
myage()

 

 

 

●多个装饰器的执行顺序

@a
@b
@c
def func():
    print "aaa"

●等同于

a(b(c(fun)))

 

posted @ 2018-06-06 17:50  33-林俊凯  阅读(64)  评论(0)    收藏  举报