装饰器要这么去使用

装饰器是python中很重要的一个知识,但是对于多数新手来说,装饰器有点难以理解,所以看到装饰器就想绕道而走,但是这会给自己留下一个很大的隐患,一旦面试官要你实现一个简单的装饰器的时候,你就会因为这个坑而错失机会。

    那么我们为什么要使用装饰器呢?因为程序编写讲究一个OCP原则,也就是开闭原则,拒绝已完成功能的代码的修改,推荐代码功能的拓展,所以这也意味着,我们不能够再去修改已经完成的功能,而是要去扩展他的功能,因此,我们需要使用到装饰器来做这个扩展功能。我们会围绕创建普通装饰器、创建通用装饰器、多个装饰器的装饰、携带参数的装饰器及装饰递归函数来开展话题。

一. 普通装饰器的创建

    首先装饰器的创建需要满足什么条件?装饰器本质上是一个闭包,所以它也符合闭包的三个特性:

  • 函数嵌套
  • 内部函数需要用到外部函数的变量或者参数
  • 外部函数返回内部函数的函数对象
import time
def fun_out(num):
    def fun_inner():
        begin = time.time()
        print(num)
        end = time.time()
        print('程序执行时间为%s' % (end - begin))
    return fun_inner

那么现在这个高级函数就是一个闭包函数了。

那么既然有装饰器的使用,我们当然也需要一个需要被装饰的目标函数了。

def fun():
    for i in range(200000):
        print(i)

现在准备工具已经完成,那么我们怎么让上面的闭包函数成为一个具有计算程序运行时间的装饰器呢?

  • 外层函数的参数必须也只能接受需要被装饰的目标函数的函数对象
  • 内层函数中要有外层函数的参数的函数调用
import time
def fun_out(fun):
    def fun_inner():
        begin = time.time()
        res = fun()
        end = time.time()
        print('程序执行时间为%s' % (end - begin))
    return fun_inner

现在我们的这个函数就成为了一个装饰器函数。我们来调用程序看看这个结果。

import time
def fun_out(fun):
    def fun_inner():
        begin = time.time()
        fun()
        end = time.time()
        print('程序执行时间为%s' % (end - begin))
    return fun_inner
def fun():
    for i in range(200000):
        print(i)
f = fun_out(fun)
f()
执行结果:
0
.
.
.
199999
程序执行时间为0.6352810859680176

    那么这就是我们的执行结果了,可以看到我们完成了程序的添加,但是并没有对程序本身功能做更改。但是这么写不是太友好,如果说使用人员使用这个程序,步骤的增多会降低体验度,同时,这样也会把开发人员的目光吸引到如何调用装饰器上,而不是针对目标函数做装饰,所以我们一般是使用语法糖的写法。

import time
def fun_out(fun):
    def fun_inner():
        begin = time.time()
        fun()
        end = time.time()
        print('程序执行时间为%s' % (end - begin))
    return fun_inner
@fun_out
def fun():
    for i in range(200000):
        print(i)
fun()
执行结果:
0
.
.
.
199999
程序执行时间为0.6352810859680176

    这样的使用是更加的便捷的。

二. 通用装饰器的创建

    通用装饰器,即被装饰的函数是否携带参数都可以使用,则是通用装饰器。

import time
def fun_out(fun):
    def fun_inner(*args, **kwargs):
        begin = time.time()
        res = fun(*args, **kwargs)
        end = time.time()
        print('程序执行时间为%s' % (end - begin))
        return res
    return fun_inner
@fun_out
def fun1(a, b):
    time.sleep(1)
    return a + b
r = fun1(1, 2)
print(r)
执行结果:
程序执行时间为1.0004768371582031
3

    我们给内部函数添加不定长参数,不管目标函数是否携带参数都是可以直接使用此装饰器。

三. 多个装饰器的使用

    大家都知道了使用一个装饰器,我们的目标函数是没有问题的,那么如果我们使用多个装饰器是怎么样的呢?

import time
def fun_out(fun):
    def fun_inner(*args, **kwargs):
        begin = time.time()
        res = fun(*args, **kwargs)
        end = time.time()
        print('程序执行时间为%s' % (end - begin))
        return res
    return fun_inner
def login(fun):
    def wrapper(*args, **kwargs):
        print('请先登录')
        res = fun(*args, **kwargs)
        return res
    return wrapper
@fun_out
@login
def fun1(a, b):
    time.sleep(1)
    return a + b
r = fun1(1, 2)
print(r)
运行结果:
请先登录
程序执行时间为1.0000872611999512
3

    那么大家有没有注意到程序的执行顺序问题,装饰器的执行顺序是从下往上执行的,并不是我们想象中的从上往下执行,这个是多个装饰器同时装饰一个目标函数的特殊性质,哪个装饰器离目标函数近,那么那个装饰器就先执行。

四. 携带参数的装饰器的使用

    携带参数的装饰器是一个比较特殊的装饰器了,当我们使用的装饰器是携带了参数的时候,我们必须在装饰器的外层在包裹一层函数才可以。

def logging(sign):
    def fun_out(fun):
        def fun_inner(num1, num2):
            if sign == "+":
                print("正在调用加法运算")
            elif sign == "-":
                print("正在调用减法运算")
            res = fun(num1, num2)
            return res
        return fun_inner
    return fun_out
@logging("+")
def add(a, b):
    result = a + b
    return result
@logging("-")
def sub(a, b):
    result = a - b
    return result
res = add(1, 2)
print(res)
res = sub(1, 2)
print(res)

    其实也是同样的道理,既然装饰器的外层函数只能接受目标函数对象这一个参数,那么我们就必要要用另外一个函数去接受这个参数才可以,所以直接用一层函数来包裹装饰器就可以了,当然我们在最后要返回装饰器的外层函数对象。

五. 装饰器装饰递归函数

    递归函数的装饰是一个比较特殊的现象,装饰器有个机制,当它发现此函数已经被装饰了之后,那么它就不会在装饰这个函数,而是会直接停止装饰程序,归其原因还是因为装饰器的内存原理。

    装饰器装饰一个函数,会把目标函数的对象保存到开辟的内存空间中,所以当内存空间已经存在了这个被装饰的目标函数的对象的时候,那么程序会直接报错。所以我们不能够直接使用装饰器装饰递归函数,而是要使用一个外层函数先包裹住递归函数,让闭包函数在此函数中运行完了之后再结束运行,这个时候,装饰器中就不会出现重复的函数对象了。

import time
def fun_out(fun):
    def fun_inner(*args, **kwargs):
        begin = time.time()
        res = fun(*args, **kwargs)
        end = time.time()
        print('程序执行时间为%s' % (end - begin))
        return res
    return fun_inner
@fun_out
# 求n的m次幂
def decorator(n, m):
    def fun(n, m):
        time.sleep(1)
        if m == 1:
            return n
        return n * fun(n, m - 1)
    return fun(n, m)
res = decorator(10, 5)
print(res)
执行结果:
程序执行时间为5.0015575885772705
100000

以这样的方式,我们就可以完成对递归函数的装饰了。

你学废了吗?

你可能感兴趣的:(python,程序员)