彻底弄懂装饰器

最近博主在学习装饰器,也搜集了很多的资料,找到了很多的解释,这里我举一个关于装饰器例子:我们每个人每天都要使用鼻子进行呼吸,但是每当遇到极端天气或者污染严重或者防止病毒(比如这次疫情),就没有办法进行有效的保护。我们总不能把我们的鼻子给改造了,然后在额外的提供这些额外的功能,显然这是不可能的。于是我们就想到了一个方法,不影响鼻子原本的功能和属性的前提下,直接在鼻子外面戴口罩,这样做的好处就是,鼻子仍然是鼻子,口罩还是口罩,但是还能够给我提供额外的功能。

装饰器

再说装饰器之前,我们要了解Python皆可为对象。例如:Python 中的函数和 Java、C++不太一样,Python 中的函数可以像普通变量一样当做参数传递给另外一个函数。

def function(test):
    test()


def test():
    print('test')


function(test)

我们运行程序,会发现输出的结果是

test

装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

我们看一个简单的例子,例如我们想要打印0-10000这几个数字,想要看打印的效率怎么样。

def prn_nums():
    for i in range(10001):
        print(i)


prn_nums()

方法1

我们完全可以调用一个python的time库,然后截取运行前后的时间,可以得到效率。

import time


def prn_nums():
    t1 = time.time()
    for i in range(10001):
        print(i)
    t2 = time.time()
    print(t2 - t1)


prn_nums()

然后我们看一下输出的效果怎么样。

...... #省略
9997
9998
9999
10000
0.042853593826293945

可以发现此时运行程序的时间是0.04s。但是我们能够发现,这段代码的可读性非常的低,而且如果有成千个相同的需求,难道我们每次都要执行相同的过程,然后改造原有的函数?所以我们引入 装饰器

方法2 简单装饰器

我们先写一个装饰器

import time


def display_time(func):
    def wrapper():
        t1 = time.time()
        func()  # 把 prn_nums 当做参数传递进来时,执行func()就相当于执行prn_nums()
        t2 = time.time()
        print(t2 - t1)
    return wrapper


def prn_nums():
    for i in range(10001):
        print(i)


# 因为装饰器 display_time(func) 返回的时函数对象 wrapper,这条语句相当于  prn_nums = wrapper
prn_nums = display_time(prn_nums)
prn_nums()  # 执行prn_nums()就相当于执行 wrapper()

display_time就是一个装饰器,它一个普通的函数,它把执行真正业务逻辑的函数 func 包裹在其中,看起来像 prn_nums 被 display_time装饰了一样,display_time返回的也是一个函数,这个函数的名字叫 wrapper。在这个例子中,函数进入和退出时 ,被称为一个横切面,这种编程方式被称为面向切面的编程。

@ 语法糖

如果你接触 Python 有一段时间了的话,想必你对 @ 符号一定不陌生了,没错 @ 符号就是装饰器的语法糖,它放在函数开始定义的地方,这样就可以省略最后一步再次赋值的操作。

import time


def display_time(func):
    def wrapper():
        t1 = time.time()
        func()  # 把 prn_nums 当做参数传递进来时,执行func()就相当于执行prn_nums()
        t2 = time.time()
        print(t2 - t1)
    return wrapper


@display_time
def prn_nums():
    for i in range(10001):
        print(i)


prn_nums()

如上所示,有了 @ ,我们就可以省去prn_nums = display_time(func)这一句了,直接调用 prn_nums() 即可得到想要的结果。prn_nums() 函数不需要做任何修改,只需在定义的地方加上装饰器,调用的时候还是和以前一样,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。

装饰器在 Python 使用如此方便都要归因于 Python 的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

逻辑函数参数传递

可能有人问,如果我的业务逻辑函数 foo 需要参数怎么办?比如:

def foo(name):
    print("i am %s" % name)

我们可以在定义 wrapper 函数的时候指定参数:

def wrapper(name):
        logging.warn("%s is running" % func.__name__)
        return func(name)
    return wrapper

这样 foo 函数定义的参数就可以定义在 wrapper 函数中。这时,又有人要问了,如果 foo 函数接收两个参数呢?三个参数呢?更有甚者,我可能传很多个。当装饰器不知道 foo 到底有多少个参数时,我们可以用 *args 来代替:

def wrapper(*args):
        logging.warn("%s is running" % func.__name__)
        return func(*args)
    return wrapper

如此一来,甭管 foo 定义了多少个参数,我都可以完整地传递到 func 中去。这样就不影响 foo 的业务逻辑了。这时还有读者会问,如果 foo 函数还定义了一些关键字参数呢?比如:

def foo(name, age=None, height=None):
    print("I am %s, age %s, height %s" % (name, age, height))

这时,你就可以把 wrapper 函数指定关键字函数:

def wrapper(*args, **kwargs):
        # args是一个数组,kwargs一个字典
        logging.warn("%s is running" % func.__name__)
        return func(*args, **kwargs)
    return wrapper

带参数的装饰器

带参数的装饰器只需要在原来那个不带参数的装饰器基础上之上在最外层套一个函数,该函数中定义一个参数,然后嵌套函数中引用该参数即可实现。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性

例如:

import time


def display_time(s):
    print(s)
    def decorator(func):
        def wrapper():
            t1 = time.time()
            func()  # 把 prn_nums 当做参数传递进来时,执行func()就相当于执行prn_nums()
            t2 = time.time()
            print(t2 - t1)
        return wrapper
    return decorator


@display_time(s="running")
def prn_nums():
    for i in range(10001):
        print(i)


prn_nums()

上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我们调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。

类装饰器

装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

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

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')

@Foo
def bar():
    print ('bar')

bar()
原创文章 103 获赞 391 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_43906799/article/details/106169352