Python之装饰器

Python之装饰器

装饰器简单理解就是在多层函数嵌套时用@语法糖来代替一层函数。

装饰器简单理解

  • 常见的三种装饰器的类型
    • 无参数装饰器
    • 有参数装饰器
    • 无参类装饰器
    • 有参类装饰器
  • 总结
  • 参考文献

1. 无参数装饰器

  举常见的两个无参数例子:
  (1)打印函数运行时间
  (2)日志的打印

1、(打印函数的运行时间)

from time import time
def count(func):
    def wrapper(*args , **kw):
        start = time()#起始时间
        res = func(*args , **kw)
        end = time()#结束时间
        print('run time is {}'.format(end-start))
    return wrapper

@count
def create_list(n):
    return [i for i in range(n)]#创建一个从0到n-1的列表

res = create_list(10000000)
"""result: run time is 1.6294074058532715 """

由上述例子可以发现三点:@count等价于count(create_list(n));再不改变create_list()方法就能获得函数的运行时间;@count可以装饰任何函数,获得该函数的运行时间。

2、(日志的打印)

# import functools
def logger(func):
    # @functools.wraps(func)
    def wrapper(*args ,**kw):
        print("开运运行名为 {} 的函数".format(func.__name__))
        res = func(*args ,**kw)
        print("运行结果为:{}".format(res))
        print("该函数运行结束")
    return wrapper

@logger
def add(a,b):
    c=a+b
    print(add.__name__)#wrapper
    return c

add(2,5)
"""result:
开始运行名为 add 的函数
wrapper
运行结果为:7
该函数运行结束
"""

这个例子利用无参装饰器实现了简单的日志打印,但发现add函数中:print(add.name) 输出的结果为wrapper,而不是add,那么要如何解决装饰器带来的影响呢?
这时可以调用functools库来解决该问题,实例如下:

import functools
def logger(func):
    @functools.wraps(func)
    def wrapper(*args ,**kw):
        print("开运运行名为 {} 的函数".format(func.__name__))
        res = func(*args ,**kw)
        print("运行结果为:{}".format(res))
        print("该函数运行结束")
    return wrapper

@logger
def add(a,b):
    c=a+b
    print(add.__name__)#add
    return c

add(2,5)
"""result:
开运运行名为 add 的函数
add
运行结果为:7
该函数运行结束
"""

从宏观的来看,可以发现在我们定义的装饰器logger中又引入了一个装饰器:@functools.wraps(func);该装饰器的基本功能就是能够还原新传入的函数的原有属性。
再稍微深入探究,查看functools.wraps的源码,发现wraps返回了partial类,而该类实现了复制并保存传入原函数的属性。
Python之装饰器_第1张图片
更详细的了解点击这里

2. 有参数装饰器

装饰器不仅能传函数,也可以传入其它参数,则此时就需要至少三层嵌套函数来实现装饰器的传参功能。例子如下:

import functools

def log_is_hello(text):
    def decorate(func):
        @functools.wraps(func)
        def wrapper(*args , **kw):
            print("该函数名为:{}".format(func.__name__))
            print("传入的参数*args为:{}".format(*args))
            print("log :{} {}".format(text,*args))
            return func(*args ,**kw)
        return wrapper
    return decorate

@log_is_hello("hello")
def text_is_hello(word):
    print(text_is_hello.__name__)

text_is_hello('world')
"""result:
该函数名为:text_is_hello
传入的参数*args为:world
log :hello world
text_is_hello
"""

这咋一看优点复杂,多层嵌套,比较难明白是如何将text_is_hello函数传入的;其实对装饰器进行拆分就容易理解很多了:

decorate = log_is_hello("hello")
wrapper = decorate(text_is_hello)
wrapper("world")
"""result:
该函数名为:text_is_hello
传入的参数*args为:world
log :hello world
text_is_hello
"""

这个流程输出结果一样,更容易明白:
(1)先将参数传入到装饰器中,得到带有"word"的参数的decorate装饰器;
(2)再将函数text_is_hello传入到decorate装饰器中,得到函数wrapper,得到步骤一传入的参数和步骤二传入的函数方法;
(3)调用wrapper方法并传入参数。

3. 无参类装饰器
还可以用类来实现装饰器,该类必须要实现__init__和__call__俩个内置函数。例子如下

class Say(object):
    def __init__(self,func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("开始运行{}函数".format(self.func.__name__))
        self.func(*args,**kwargs)
        print("函数运行结束")
        # return self.func(*args , **kwargs)

@Say
def log_is_call(who):
    print("{0}说我的函数名为:{1}".format(who,log_is_call.func.__name__))

log_is_call("Mg")
"""reslut:
开始运行log_is_call函数
Mg说我的函数名为:log_is_call
函数运行结束
"""

由上述例子就可以明白,为什么类必须实现__init__和__call__两个内置函数。其实就是__init__用于获取传入函数方法;__call__用于获取传入函数的参数和做一系列其它操作。这个类装饰器实现的方法不止这一种,往下看就明白了。

4. 有参类装饰器
最常见的类往往需要传入一些参数,例子如下:

class Say(object):
    def __init__(self,age = None):
        self.age = age

    def __call__(self,func):
        def wrapper(*args , **kwargs):
            print("开始运行{}函数".format(func.__name__))
            func(*args, **kwargs)
            print("我的年龄是:{}".format(self.age))
            print("函数运行结束")
        return wrapper

@Say(age = 22)
def log_is_call(who):
    print("我的名字叫:{}".format(who))

log_is_call("Mg")
"""result:
开始运行log_is_call函数
我的名字叫:Mg
我的年龄是:22
函数运行结束
"""

看到这个就联想到上面第二种有参的函数装饰器类型,可以和其类比,唯一不同的是其装饰器的参数是在__init__中传入的。

总结
一句话总结,装饰器就是用@前缀+函数名表示,用于不改变函数本身,而是将其放入另一个函数中,这就是装饰器的基本思想。

参考文献
1.https://www.jianshu.com/p/ee82b941772a
2.https://www.jb51.net/article/168276.htm
3.https://zhuanlan.zhihu.com/p/45535784

你可能感兴趣的:(python)