装饰器_decorator(详解、扫盲、入门)



装饰器
本质:就是函数。
作用:为其他函数添加[附加]功能。
原则:1.不能修改[被装饰]的函数的源代码结构。
2.不可以更改函数的调用方式。

假如说我们要给一个或个函数添加一个新功能:统计函数运行的时间
import time
import functools
def run_time(func):
    @functools.wraps(func)
    def warpper():
        startTime = time.time()
        res = func()
        stopTime = time.time()
        print('the %s running time is' % func.__name__, (stopTime - startTime))
        return res
    return warpper
@run_time
def test_1():
    time.sleep(1)
    print('in test_1')

test_1()

 

了解装饰器之前我们要回顾一下三个基本小知识:
1.函数也可以看做是变量(对象)
2.高阶函数:将函数作为实参传给另一个函数,返回值中包含函数名
3.嵌套函数:在函数体内创建函数并使用

1、函数 = '变量':
def test_1():
    time.sleep(1)
    print('in test_1')
当我们定义一个函数的时候,就像是定义一个变量一样,test_1就是函数名字,而下边的函数体可以看做是test_1的值
让我们来看看打印test_1会发生神马事情
print(test_1)
<function test_1 at 0x10237c7b8>
这个test_1的函数名里边存着的是函数体的地址:0x10237c7b8
而当函数名test_1后边加上两个括号的时候就是执行这个函数(这个大家都懂),那我们如果说打印test_1()会发生什么事情呢?
print(test_1())
>>> in test_1
>>> None
因为这个test_1()函数中,是没有返回值的,所以执行的时候就是先执行函数test_1()--> in test_1,然后因为没有返回值,所以说打印出来的是None
所以run_time()函数执行之后,最后返回的是warpper这个函数的地址。

2、高阶函数:
1.将函数名作为实参传送给另一个函数(就是上变说的把函数当做变量传递一样)
2.返回值中包含变量名
例:
def test_1():
    time.sleep(1)
    print('in test_1')

def run_time(func):
    startTime = time.time()
    func()
    stopTime = time.time()
    print('the %s running time is' % func.__name__, (stopTime - startTime))

run_time(test_1)
这样我们就满足了装饰器其中一项是不修改被装饰函数的源代码.但是这样一来还是不满足第二个条件->不修原函数的调用方式.
而我们恰恰可以通过高阶函数的第二个(返回值中包含函数名)的方式来满足这个方式
def run_time(func):
    startTime = time.time()
    func()
    stopTime = time.time()
    print('the %s running time is' % func.__name__, (stopTime - startTime))
    return func

def test_1():
    time.sleep(1)
    print('in test_1')
因为在run_time(func)执行结束之后会把函数名作为返回值返还,那我们用一个变量接收的话就是
test_x = run_time(test_1)
print(test_x)
in test_1
the func running time is 1.0035369396209717
<function test_1 at 0x101b7c7b8>
看到了吗?test_x实际上接收的就是test_1函数,那么我们是不是可以这样,给test_1重新赋值
然后我们执行test_1
test_1 = run_time(test_1)
test_1()
in test_1
the func running time is 1.0048449039459229
in test_1
但是为什么会执行两次? 原因是在给test_1重新赋值的过程中,是先执行了test_1(),然后返回test_1()的函数名[test_1]
但这样还是没有达到在不修改原函数代码和调用方式下添加新功能啊,那我们在做一下小改动.
3.嵌套函数:在函数体内定义一个函数并调用.
def run_time(func):
    def warpper():
        startTime = time.time()
        ret = func()
        stopTime = time.time()
        print('the %s running time is'% func.__name__,(stopTime - startTime))
        return ret
    return warpper
test_1 = run_time(test_1) # 相当于@run_time
这样修改之后再调用呢,就是:你大爷还是你大爷,你大娘已经换人了.
然后执行test_1()就可以了.
OK 到这里,一个最基本的装饰器就完成了;这里回顾一下:
函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
def test_1():
    print('in test_1')
foo = test_1
foo()
>>> in test_1
现在,我们要增强test_1()函数的功能,比如,测试函数执行时间,但又不希望修改test_1()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
def run_time(func):
    def warpper():
        startTime = time.time()
        res = func()
        stopTime = time.time()
        print('the %s running time is' % func.__name__, (stopTime - startTime))
        return res
    return warpper
只需要在下边原函数定义的前边加上@run_time就可以了
@run_time
def test_1():
    print('in test_1')
但是如果原函数有参数的话,那该怎么办?
上边提到过一些test_1 = run_time(test_1)就相当于@run_time,我们直接掉用test_1()就可以了,但是重新给test_1赋值之后的test_1又是什么呢?我们来打印一下test_1的名字
print(test_1.__name__)
>>> warpper
重新赋值后的是变成了装饰函数中的warpper了,所以如果需要参数的话我们只需要在这个函数上添加参数就可以啦~
def run_time(func):
    def warpper(*args, **kw):
        startTime = time.time()
        res = func(*args, **kw)
        stopTime = time.time()
        print('the %s running time is' % func.__name__, (stopTime - startTime))
        return res
    return warpper
以上的装饰器已经能够应付90%的情况了但是,还有一种装饰器很强大
带参数的装饰器
装饰器还有更大的灵活性,例如可以给他添加参数来应付更复杂的情况,装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许我们在调用时,提供其它参数,比如@run_time(a)。这样
就为装饰器的编写和使用提供了更大的灵活性。
def run_time(arg):
    def outer_warpper(func):
        def warpper(*args, **kw):
            startTime = time.time()
            res = func(*args, **kw)
            stopTime = time.time()
            print('the %s running time is' % func.__name__, (stopTime - startTime),arg)
            return res
        return warpper
    return outer_warpper

@run_time('皮皮虾我们走~')
def test_1(*args, **kw):
    print('in test_1',args,kw)
    return 'abc'

test_1()

>>> in test_1 () {}
>>> the test_1 running time is 2.4080276489257812e-05 皮皮虾我们走~

functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,例子:
def outer_warpper(func):
    def warpper(*args, **kw):
        """A wrapper function."""
        startTime = time.time()
        res = func(*args, **kw)
        stopTime = time.time()
        print('the %s running time is' % func.__name__, (stopTime - startTime))
        return res
    return warpper

@outer_warpper
def test_1():
    """A TEST FUNCTION."""
    print('in test_1')

print(test_1.__name__)
print(test_1.__doc__)

>>> warpper
>>> A wrapper function.
如果说有别的人想要调用test_1函数的__doc__ ,__name__,那么就用到了functools了
import functools #@functools.wraps(func)
def outer_warpper(func):
    @functools.wraps(func)
    def warpper(*args, **kw):
        """A wrapper function."""
        startTime = time.time()
        res = func(*args, **kw)
        stopTime = time.time()
        print('the %s running time is' % func.__name__, (stopTime - startTime))
        return res
    return warpper

@outer_warpper
def test_1():
    """A TEST FUNCTION."""
    print('in test_1')
print(test_1.__name__)
print(test_1.__doc__)
>>> test_1 >>> A TEST FUNCTION.

这样,关于装饰器的入门就了OK了~

posted @ 2017-04-29 08:51  LeeeetMe  阅读(299)  评论(0编辑  收藏  举报