Python装饰器(decorator)

装饰器语法是Python中一个很重要的语法,刚学Python时就有接触,但当时理解起来很困难,不过最近这一次学习装饰器的时候豁然开朗,可能是看的次数多了吧,于是记录下来,加深对装饰器的理解。

装饰器是一种设计模式,我买了一本设计模式的书还没有开始看,关于设计模式这一块以后再讨论。

装饰器是用于给一些已有的函数添加一些额外功能的,比如计时、日记、计数等等。这些功能其实也是可以在已有函数中直接实现的,但是为什么不这样做呢?大概有下面这些原因:

  1. 已有函数遍布整个项目,修改起来很麻烦,且容易出错
  2. 已有函数为第三方库,无法修改
  3. 需要添加的功能与函数本来的逻辑没有任何关系,将其添加进原函数不合适。

装饰器基础

理解装饰器首先要知道在Python中,函数也是一个对象。像String,List,tuple这些数据结构一样,可以作为其他函数的参数传递。

def test(func):
    print('begin')
    func()
    print('after')

def func_a():
    print("i'm func_a")

test(func_a)
# output:
# begin
# i'm func_a
# after

其次需要知道的是函数也可以定义在另一个函数的内部并且可以被返回。

def test():
    print('1111')
    def inner():
        print('22222')
    return inner

a = test()
# output: 1111 注意这时已经有输出了
a()
# output: 2222

decorator语法糖

有了上面那些基础知识,我们已经有能力理解装饰器了。既然装饰器是语法糖,肯定是简化后的语法。那么他没化简之前是一个什么模样呢?

def decorator(a_func_to_decorator):
    def wrapper():
        # do something before func
        print('begin------')
        a_func_to_decorator()
        # do something after func
        print('after------')
    return wrapper

def func_to_decorator():
    print("chenfeikun zhen shuai")

func_to_decorator = decorator(func_to_decorator)
func_to_decorator()
# output:
# begin------
# chenfeikun zhen shuai
# after------

上面这就是一个最简单的装饰器了,以后遇到每一个需要装饰的函数,只要将这个函数作为参数重新传入decorator函数就行。但是这样写不好看啊,所以Python就推出了一个更漂亮的写法。

def decorator(a_func_to_decorator):
    # 如上

@decorator
def func_to_decorator():
    print("chenfeikun tai shuai le")

func_to_decorator()
# output:
# begin------
# chenfeikun tai shuai le
# after------

所以@decorator是对func_to_decorator = decorator(func_to_decorator)的一种简写,也就是语法糖。

传递参数给被装饰的函数

当然我们需要被装饰的函数一般不会这么简单,这个被装饰的函数可能是需要参数的,那么如何把这些参数原封不动的传递给被装饰的函数呢?因为被装饰函数已经作为最外层函数的参数,所以只能放在里层的wrapper函数里了。

def decorator(a_func_to_decorator):
    def wrapper(a, b):
        print('begin-----')
        print(a_func_to_decorator(a, b))
        print('after-----')
    return wrapper

@decorator
def add(c, d):
    return c+d

add(5, 9)
# output:
# begin-----
# 14
# after-----

方法与函数

上面我们提到的都是函数,如果类里的方法也需要装饰呢?其实方法和函数几乎是一样的,唯一的区别就是方法需要一个self参数。上面也提到了如何传参数,所以很容易就可以写出下面的方法装饰器。

def decorator(a_method_to_decorator):
    def wrapper(self, name):
        print('begin-----')
        a_method_to_decorator(self, name)
        print('after-----')
    return wrapper

class Car(object):
    @decorator
    def talk(self, name):
        print('Hi, {}. dududu~~~'.format(name))

audi = Car()
audi.talk('chenfeikun')
# output:
# begin-----
# Hi, chenfeikun. dududu~~~
# after-----

正因为函数和方法是如此的相似,我们有时侯可能想要写一个适用于两者的装饰器。那就别忘了Python还有一个重要的特性,它可以传递可变个数的参数,即*args**kwargs,这两个参数的含义应该都知道,不知道的可以去看一下,很简单。所以我们又可以写出函数和方法通用的装饰器。

def decorator(something_to_decorator):
    def wrapper(*args, **kwargs):
        print('begin-----')
        print('args: {}'.format(args))
        print('kwargs: {}'.format(kwargs))
        something_to_decorator(*args, **kwargs)
        print('after-----')
    return wrapper

class Car(object):
    @decorator
    def talk(self, *args, **kwargs):
        print('talk-args: {}'.format(args))
        print('talk-kwargs: {}'.format(kwargs))

@decorator
def talk_2(name):
    print('my name is {}'.format(name))

audi = Car()
audi.talk(1,2,3,a='a',b='b')
# output:
# begin-----
# args: (<__main__.Car object at 0x7ff7c0e46240>, 1, 2, 3)
# kwargs: {'b': 'b', 'a': 'a'}
# talk-args: (1, 2, 3)
# talk-kwargs: {'b': 'b', 'a': 'a'}
# after-----
talk_2('chenfeikun')
# output:
# begin-----
# args: ('chenfeikun',)
# kwargs: {}
# my name is chenfeikun
# after-----

传递参数给装饰器

如果想向外层的那个decorator函数传递参数呢,这能不能做到呢?当然是可以的,不过外层的decorator函数和内层的wrapper函数的参数都已经被上面提到的功能使用了,哪里还有位置放置新的参数呢?

这其实和装饰器的概念理解起来是一样的,decorator函数可以返回一个wrapper函数,那么我们可以用一个更外层的函数A(不会取名字了,将就一下)来包住decorator函数,并将其返回,A函数的作用就是返回/生成一个装饰器,那么就可以给A传递一些参数来控制如何装饰器,多说无益,代码可能看着简单一些。

def A(choose):
    def decorator_a(a_func_to_decorator):
        def wrapper():
            print('decorator_A---begin')
            a_func_to_decorator()
            print('decorator_A---after')
        return wrapper

    def decorator_b(a_func_to_decorator):
        def wrapper():
            print('decorator_B---begin')
            a_func_to_decorator()
            print('decorator_B---after')
        return wrapper

    if choose == 'a':
        return decorator_a
    return decorator_b

type_a = A('a')
@type_a
def talk_1():
    print('talk_1---dududududu~~~~~')

@A('b')
def talk_2():
    print('talk_2---dududududu~~~~~')
talk_1()
# output:
# decorator_A---begin
# talk_1---dududududu~~~~~
# decorator_A---after
talk_2()
# output:
# decorator_B---begin
# talk_2---dududududu~~~~~
# decorator_B---after

对比一下上面talk_1talk_2的写法,其实是一样的,但是后面一种更加简单,更符合装饰器的形式。

functools模块

上面的装饰器还有一个毛病,最后一个代码中的talk_2函数其实已经不是原来的talk_2函数了,因为装饰器的最初样子为

talk_2 = decorator(talk_2)

相当于talk_2指向了另外一个对象,这个对象是decorator返回的wrapper,原来与talk2绑定的一些属性就会被修改。而functools模块就是帮助我们不让这些东西被修改,下面举个例子。

def test():
    pass

print(test.__name__)
# output:
# test
def decorator(func):
    def wrapper():
        func()
    return wrapper

@decorator
def test():
    pass

print(test.__name__)
# output:
# wrapper

可以看到函数的name属性被修改了,但是用了functools模块可以解决这个问题。

import functools
def decorator(func):
    @functools.wraps(func)
    def wrapper():
        func()
    return wrapper

@decorator
def test():
    pass

print(test.__name__)
# output:
# test

参考资料

stackoverflow

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

推荐阅读更多精彩内容

  • 我对Python装饰器的理解就是用一个函数去修饰另外一个函数。 装饰器 在运行原来功能的基础上,添加一些新的功能...
    敢梦敢当阅读 254评论 0 1
  • 前言 装饰器是个令人头疼的概念,下面我用最最简单的例子和最最详细的表达来解释一下这个概念,希望能对大家有所帮助。 ...
    乐行者123阅读 415评论 0 0
  • Python进阶框架 希望大家喜欢,点赞哦首先感谢廖雪峰老师对于该课程的讲解 一、函数式编程 1.1 函数式编程简...
    Gaolex阅读 5,484评论 6 53
  • 装饰器是一种设计模式,它提供了强大的复用非业务逻辑的能力,经典的应用场景有日志记录、性能测试、事务处理等。下面分几...
    王吉吉real阅读 702评论 1 3
  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 1,443评论 0 6