从浅到深讲解python修饰器

本文为原创,转载请注明出处

从浅到深讲解python修饰器

什么是修饰器?

修饰器是一个函数,接受一个函数或方法作为其唯一的参数,并返回一个新函数或方法,其中整合了修饰后的函数或方法,并附带了一些额外的功能.[1]

上面的定义不免有点难以理解,我们来看下面的图

我们之前所理解的python执行函数过程是如图1.1的流程.

如果我们给函数添加了修饰器,那么当程序执行到函数A的时候,系统会检测到函数A上有一个修饰器,那么系统就会先执行修饰器里的过程然后再回到函数执行函数的内容.

但是从修饰器回来的那一条线路其实并不是必须的, 需要在修饰器里面编写回来的语句回到函数,接下来会讲到

修饰器的简单作用

在其定义中就已经介绍修饰器的作用是给函数增加额外的功能.

: 在修饰器的简单作用这一部分,接下来的内容我无法自己组织语言将其讲清楚,故参考了简书作者MrYun 谈谈python修饰器 的内容,个人觉得这一篇在引入修饰器作用方面的描写很棒!

从简单的一个例子讲起,现在我们有一个函数

def foo():
    print("this is a test")

foo()

现在我们需要给它进行性能测试,那么需要改成以下内容

import time


def foo():
    start = time.clock()
    print("this is a test")
    end = time.clock()
    print("start:", start, " end:", end)


foo()

如果我们希望给几十上百个函数都添加这样的性能测试,那么需要在每个函数内部开头与结尾都这样编辑吗?显然不是.

这个时候我们编写一个新的函数test(),在test的函数开头与结尾编写时间定义,将要测试的函数传入test的函数(函数也是一个变量,是可以作为参数的),在中间执行,比如以下内容

import time


def foo():
    print("this is a test")


def test(func):
    start = time.clock()
    foo()
    end = time.clock()
    print("start:", start, " end:", end)


test(foo)

现在我们就可以给每个函数进行测试了

for 函数 in 项目:
    test(函数)

如果我们将test中的输出加入到文件中,我们就可以得到每个函数的性能记录

但是, 现在我们需要给大量函数实现另一个功能: 日志功能, 也就是在项目执行过程中, 函数的每一个操作都被记录下来, 意味着每使用一次函数都要手动编写test(foo), 尤其是如果需要使用函数的返回值的时候, 这种方式就有点捉襟见肘了

这个时候修饰器的作用就显示出来了. 它可以在每个使用它的函数上进行功能的添加, 而且使用者完全感受不到他的存在, 也就是说我们使用的时候依然是foo(), 但是在内部项目却另外实现了test()的功能

这个修饰器的使用格式如下,具体内容之后会讲解

import time


def test(func):
    def wrapper():
        start = time.clock()
        func()
        end = time.clock()
        print("start:", start, " end:", end)
    return wrapper


@test
def foo():
    print("this is a test")


foo()

修饰器的使用

上面已经了解到了修饰器的作用,那么我们可以了解修饰器的格式了.

上面插入的修饰器太过突兀,我们来段过渡代码(这一段代码也是来自那篇简书)

import time

def test(func):
    def wrapper():
        start = time.clock()
        func()
        end = time.clock()
        print("start:", start, " end:", end)
    return wrapper

def foo():
    print("this is a test")


foo = test(foo)
foo()

执行过程如下

在python中, 我们把foo = test(foo)用一种更简单的形式来表达, 就是在使用装饰器的函数foo上面加上 @修饰器名称, python的修饰器是一种语法糖.

@test
def foo():
    print("this is a test")

如果需要使用到foo函数的返回值,那么test函数可以这样写

import time
def test(func):
    def wrapper():
        start = time.clock()
        print("this is a order test, if you need not it, delete it") # 用于测试执行顺序,可以跟着走一遍
        a = func()
        end = time.clock()
        print("start:", start, " end:", end)
        return a # 这种获得返回值的方法可能在多层修饰器的时候有矛盾,我先用!!!标记, 等理顺后再回来修改,如果我发布之后这里依然存在...说明我忘记了...
    return wrapper

@test
def foo():
    print("this is a test")
    return "this is a return value"

print(foo())
# 输出
# this is a test wrapper, if you need not it, delete it
# this is a test
# start: 4.44444839506524e-07  end: 1.8222238419767486e-05
# this is a return value

在《Python3程序开发指南第二版》(以下简称《指南》)中给的例子是一个对python初学者(没涉及项目)来说比较有趣的小修饰器, 有兴趣可以看看,我给它做了一点注释

def positive_result(function):
    def wrapper(*args, **kwargs):
        # result获得函数的返回值, 进行结果判断
        result = function(*args, **kwargs)
        # assert断言, 如果function函数的返回值大于等于0, 的产生一个AssertionError异常
        assert result >= 0, function.__name__ + "() result isn't >= 0"
        # 返回
        return result
    
    # 将wrapper的docstring和名称设置成和原始函数一样,有利于内省(获得自身的信息)
    wrapper.__name__ = function.__name__ 
    wrapper.__doc__ = function.__doc__
    return wrapper

# 使用positive_result修饰器
@positive_result
def discriminant(a,b,c):
    return (b**2) - (4*a*c)


print(discriminant(0,6,5))
print(discriminant(2,6,5))

《指南》中给出了这个例子的简化版, 使用到了functools.wraps(function)

def positive_result(function):
    # wrapper本身使用functools模块的@functools.wraps进行包裹, 这可以确保wrapper的名称与docstring与function相同
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        result = function(*args, **kwargs)
        assert result >= 0, function.__name__ + "() result isn't >= 0"

        return result
    return wrapper

修饰器参数化

现在我们已经了解到了什么是修饰器以及修饰器的基本使用, 那么在上面的日志修饰器上, 我们的日志信息往往是要写入文件内,但是不同的函数需要写进的文件名不一样, 那么简单的 @修饰器名称已经没法满足需要了, 这个时候就需要修饰器参数化, 即将要操作的文件名传递给test()函数

现在放一个《指南》中给出的例子

import functools
def bounded(mininum, maxinum):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(*args, **kwargs):
            result = function(*args, **kwargs)
            if result < mininum:
                return mininum
            elif result > maxinum:
                return maxinum
            return result
        return wrapper
    return decorator

@bounded(0,100)
def percent(amount, total):
    return (amount / total) * 100

percent(15,100)
  • 执行过程如下

(https://img2018.cnblogs.com/blog/1075950/201907/1075950-20190724200254507-881949016.png)

1. percent函数被执行, 检测到有bounded修饰器, 

    ~~~py
    # 好忙啊啊啊啊啊,未完待续,如果你能看见这句话,,说明我忘记回来补了嘤嘤嘤
    ~~~

  1. 《Python3程序开发指南第二版》第八章P311 ↩︎

posted on 2019-07-24 20:01  子狼  阅读(12148)  评论(3编辑  收藏  举报

导航