Python装饰器

# 这是一个装饰器的简单例子

@dec
def func():
    pass

什么是装饰器

装饰器可以在不改变原有对象的代码及调用方式的情况下,为原有的对象增加新的功能或限制条件。装饰器有函数装饰器,也有类装饰器。装饰器体现的是开放封闭原则,即对功能扩展的开放,对修改已实现功能的封闭。

装饰器本质上就是Python的函数,它和普通的函数没有任何区别,只是有了特殊的用法,所以在特殊用法下就有了装饰器这个名字。

装饰器怎么用

现在有一个hello函数,功能很简单,只是打印hello world!

def hello():
    print('hello world!')
    
hello()

它的执行结果为:

hello world!

现在要对这个函数进行一下功能的扩展。

def dec(func): # 这个就是装饰器,参数就是被装饰的函数
    def wrapper():
        print('start...')
        func()
        print('end...')
    return wrapper
    
@dec # 这是装饰器的用法
def hello(): 
    print('hello world!')
    
hello() #函数的调用方法和上边一样没有改变    

现在的执行结果为:

start...
hello world!
end...

如上面的代码显示,函数dec就是装饰器,函数hello就是被装饰的函数。而装饰器的用法,就是hello函数上面的@dec。注意,这里应用装饰器只是写了dec这个函数名,而不是dec()这样调用它。

装饰器的运行机制

还是上面的例子。

  1. 解释器读到函数dec,也就是装饰器。这时它还没有被调用,所以只是被存在了内存中,并没有被执行。
  2. 到了@dec。@dec和dec()一样,都会执行这个函数,但是不同的是,@dec会把被装饰的hello函数的本身(而不是函数执行结果)当作参数,传入到dec函数中,相当于执行了dec(hello)这个函数。
  3. 这时开始执行dec函数。首先会把wrapper函数存入到内存中(并不是执行),然后返回wrapper函数本身(也不是执行)给hello函数。也就是,hello函数被装饰完之后,内存地址被指向到了wrapper函数的内存地址。
  4. 执行hello函数。由于hello函数的地址已经被指向到了wrapper函数,所以执行结果就是wrapper函数的执行结果。wrapper函数中的func这个函数地址才是原来hello函数的内存地址。所以使用装饰器之后,表面上不会改变原来函数调用的方法。

以上就是装饰器的运行机制。

为什么是嵌套函数

刚开始学习装饰器的人可能会比较疑惑,装饰器为什么要用嵌套函数?感觉好多功能不用嵌套也可以实现。下面看例子。

def dec(func):    
    print('start...')    
    func()    
    print('end...')

@dec
def hello():    
    print('hello world!')

执行结果为:

start...
hello world!
end...

这个执行结果看起来没有问题。是的,执行结果是没有问题,但是问题是还没有写执行语句却已经有了执行结果。

造成这个问题的原因就在@dec这里。@dec就是执行装饰器的语句,所以它就执行了dec这个函数。这就是为什么,还没有写执行语句却有了执行结果的原因。这也是为什么装饰器要用嵌套函数的原因,再用一个函数封装一下,避免在定义阶段就给执行了。

被装饰的函数带参数

def hello(string):
    print(f'hello {string}!')
    
    
hello('python')

执行结果为:

hello python!

这是一个带参数的函数,那么这种函数如何添加装饰器呢?

其实,只要理解了被装饰的函数会被指向到装饰器里的函数,那么在装饰器里的函数加上参数就解决了传参的问题。下面看例子。

def dec(func):
    def wrapper(string): # 添加和hello一样的参数即可
        print('start...')
        func(string) # 通过wrapper函数传递来的参数
        print('end...')
    return wrapper

@dec
def hello(string): # 内存地址会被指向wrapper的内存地址
    print(f'hello {string}!')

hello('python')

执行的结果为:

hello python!

装饰器带参数

如果理解了前边的内容,那么理解装饰器带参数也就比较容易了。下面先看例子。

def outer(name): # 多了一层函数,用于接收传递装饰器的参数
    def dec(func):
        def wrapper(string):
            print(f'{name} start...')
            func(string)
            print(f'{name} end...')
        return wrapper
    return dec

@outer('haha')  # 装饰器执行语句多了括号和参数
def hello(string):
    print(f'hello {string}!')

hello('python')

执行结果为:

haha start...
hello python!
haha end...

首先可以看到,装饰器在定义的时候又多了一层嵌套,这层嵌套用于接收传递参数,并且返回下一层的嵌套函数。

然后可以看到装饰器多了括号和参数。现在说的就是带参数的装饰器,所以带参数没有什么稀奇的。

最主要的是多的那个括号。装饰器执行语句多了括号之后,它的意思也稍微有了变化。我们知道,执行函数就是函数名加上括号。那么 @outer('haha') 的执行顺序是下面这样的。

  1. 先执行outer('haha')这个函数,这时它的参数是'haha'。
  2. outer函数返回值是dec,所以@outer('haha') = @dec,并把参数传到了函数内。
  3. @dec这个语句就是我们前边熟悉的装饰器的执行语句了。它会把hello函数本身当作参数传递给函数dec,并把内存地址指向到了wrapper函数。后边执行过程就跟前边的例子一样,也就不再赘述了。

主要知道@outer()会先执行outer函数,而不是把hello当作参数的装饰器语句,那么对于带参数的装饰器也就没什么难点了。

叠加装饰器

装饰器也可以叠加使用,还是先看例子。

def dec1(func):
    print('这是第一层')
    def wrapper():
        print('这是第1层开始')
        func()
        print('这是第1层结束')
    return wrapper

def dec2(func):
    print('这是第二层')
    def wrapper():
        print('这是第2层开始')
        func()
        print('这是第2层结束')
    return wrapper

@dec2
@dec1
def hello():
    print('hello world!')

print('开始')
hello()

执行结果为:

这是第一层
这是第二层
开始
这是第2层开始
这是第1层开始
hello world!
这是第1层结束
这是第2层结束

这个结果可能刚开始看有些不那么好理解,我们慢慢看。

先看执行结果的前三行。这三行内容里,首先执行的是两个装饰器的内容,之后才是print语句。从这个执行结果可以看出,python是从上到下顺序执行,并在遇到装饰器执行语句的时候会自动执行装饰器函数('第几层开始'没有跟着执行是因为被封装进了函数,要执行语句才能执行)。再从首先执行了第一层又执行了第二层可以看出,遇到叠加装饰器时是从下向上执行的。

明白了装饰器是从下向上执行,那么就是下一层装饰器的函数就是它上一层装饰器的参数。

@dec2  # 相当与dec2(dec1(hello))
@dec1  # 相当于dec1(hello)
def hello():

这么看这个执行结果就好理解了。

结束

这篇文章主要是帮助初步理解装饰器和一些简单的用法,在这里就不详细说明其他更深奥的用法了(主要是我不会),那么这篇文章也就到这里了。

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

推荐阅读更多精彩内容

  • 部分细节自己改了点,也加了点自己例子,基本上属于转载。转载出处:https://my.oschina.net/le...
    洛克黄瓜阅读 1,964评论 0 3
  • 在python编程中,我们经常看到下面的函数用法: with open("test.txt", "w") as f...
    hugoren阅读 801评论 0 0
  • 一、装饰器的基本使用 在不改变函数源代码的前提下,给函数添加新的功能,这时就需要用到“装饰器”。 0.开放封闭原则...
    NJingZYuan阅读 515评论 0 0
  • 在学习Python的过程中,我相信有很多人和我一样,对Python的装饰器一直觉得很困惑,我也是困惑了好久,并通过...
    愚灬墨阅读 457评论 1 1
  • 文||木暖霏 目录 李佳茗踉跄地站起来奔向被炸的四分五裂的地方,相似的场景再一次重演。五年前,十二月初的文安市,原...
    木暖霏阅读 466评论 0 6