装饰器

装饰器是python中较为高级的用法,它的使用也是比较不容易理解的。

在最近的学习中,找到一个讲解的很清楚的视频,解决了我多年的疑惑,放在文后。

之前面试的时候也问到了这个知识点,我只能连蒙带猜的胡说了一通,现在学完再回过头一看,我如果是面试官也不会自己过吧,哈哈哈。

那么现在我们就来一起学习装饰器

import time

def is_prime(num):
    if num < 2: return False
    elif num == 2: return True
    else:
        for i in range(2,num):
            if num % 2 == 0: return False
        return True

def prime_nums():
    t1 = time.time()
    for i in range(2,10000):
        if is_prime(i):
            print(i)
    t2 = time.time()
    print(t1,t2,t2-t1)

prime_nums()

首先是一段简单的代码,用来统计0-9999中的质数,没有用到装饰器,通过time模块来统计prime_nums函数运行的时间,运行一遍是9.72s。

函数本身没什么问题,但是我们的prime_nums函数是一个用来统计质数的函数,统计时间的功能在这里是突兀的,我们想要将这个功能移出函数,放在另一个函数中,这就有了第二个程序

import time

def display_time(func):
    t1 = time.time()
    func()
    t2 = time.time()
    print(t1,t2,t2-t1)

def is_prime(num):
    if num < 2: return False
    elif num == 2: return True
    else:
        for i in range(2,num):
            if num % 2 == 0: return False
        return True

def prime_nums():
    for i in range(2,10000):
        if is_prime(i):
            print(i)

display_time(prime_nums)

这个程序在原程序的基础上将计时功能抽离了出来,作为display_time函数。这个函数的参数是函数名,python是允许将函数名作为参数进行传递,而不会在传递过程中执行这个函数的,直到出现func()这样的时候才会真正执行它。将功能抽取出来后,用时2.22s,可以发现,节省了许多运行时间。

但是,有个问题,我们最后执行的是display_time(prime_nums),这里有一个主从的关系,看起来似乎主体是显示时间,但实际上显示时间只是一个附加的功能,主要的功能是找到质数。这样写不利于我们阅读程序。

同时如果保持现状,在开发完成后,我们提供给测试者和使用者的将会是display_time()这个功能,这将会带来一些歧义。而我们想提供其实是prime_nums()这样一个功能。因此,得益于python的语法糖,我们可以继续改进:

import time

def display_time(func):
    def wrapper():
        t1 = time.time()
        func()
        t2 = time.time()
        print(t1,t2,t2-t1)
    return wrapper

def is_prime(num):
    if num < 2: return False
    elif num == 2: return True
    else:
        for i in range(2,num):
            if num % 2 == 0: return False
        return True

@display_time
def prime_nums():
    for i in range(2,10000):
        if is_prime(i):
            print(i)

prime_nums()

现在我们的display_time就是一个装饰器了,他会返回一个wrapper的函数名,而这个函数实际上是对func()做了增强,增加了计时的功能。

此时,执行用@display_time装饰了的prime_nums时,实际上是执行了display_time(prime_nums)中的wrapper(),并在wrapper中执行了prime_nums()

所谓面向切面编程,就是将一个复杂的函数分为一个个小的功能,在需要修改时只需要修改一小部分,而不需要对整个的大函数进行修改。

我们的第一个装饰器跑通了,但现还要对他做一些改进:

  • 一个是如果要传参,而不是固定10000的时候
  • 一个是如果有返回值的时候
    函数就需要做一些改进,首先是处理返回值:
import time

def display_time(func):
    def wrapper():
        t1 = time.time()
        result = func()
        t2 = time.time()
        print(t1,t2,t2-t1)
        return result
    return wrapper

def is_prime(num):
    if num < 2: return False
    elif num == 2: return True
    else:
        for i in range(2,num):
            if num % 2 == 0: return False
        return True

@display_time
def prime_nums():
    count = 0
    for i in range(2,10000):
        if is_prime(i): count += 1
    return count

count = prime_nums()
print(count)

接上文所述,因为实际执行的是wrapper(),所以wrapper()必须有一个返回值,我们将func()的结果用result保存起来,再返回就可以了。

接下来是处理参数:

import time

def display_time(func):
    def wrapper(*args,**kwargs):
        t1 = time.time()
        result = func(*args,**kwargs)
        t2 = time.time()
        print(t1,t2,t2-t1)
        return result
    return wrapper

def is_prime(num):
    if num < 2: return False
    elif num == 2: return True
    else:
        for i in range(2,num):
            if num % 2 == 0: return False
        return True

@display_time
def prime_nums(max_num):
    count = 0
    for i in range(2,max_num):
        if is_prime(i): count += 1
    return count

count = prime_nums(10000)
print(count)

解决办法就是在wrapper()中将写上参数。本题中其实也可以写wrapper(max_num)和result = func(max_num),但是在处理有很多参数的函数时,使用wrapper(*args,**kwargs)的写法将所有的参数传递过去是非常方便的。

同时,在这里我们可以更加容易的看出来,
@display_time
def prime_nums(max_num):
这样的写法,其实等价于display_time(prime_nums)(max_num)。display_time(prime_nums) == wrapper,最后执行的是wrapper(max_num),只不过wrapper是在display_time中执行,能够调用func = prime_nums而已。

好了,装饰器的基础知识就学习完了,想要深入了解,重要的还是多看,多练,多思考。

大佬的视频在这里,讲得非常不错:https://www.bilibili.com/video/BV11s411V7Dt?from=search&seid=12540511450060276795

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。