从浅到深讲解python修饰器

转载自https://www.cnblogs.com/wolf-yasen/p/11240500.html

什么是修饰器?

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

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

image

我们之前所理解的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()

执行过程如下

image

在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 ↩︎
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345