详解装饰器

装饰器非常重要,不仅使代码看起来更简洁优雅,而且最大限度的复用了代码。本文主要讲解装饰器的3种用法,分别是:
1、闭包装饰器
2、类装饰器
3、Python第三方模块提供的方法
个人更推荐后面两种,可读性较好。这只是一个习惯而已,有人也觉得第一种更加简洁。

1. 闭包装饰器

闭包
闭包装饰器含有两个名词分别是闭包和装饰器,初学者可能不太清楚,在这里做一个简单的说明。闭包在很多语言中都有这个概念,是指外层函数的局部变量是内层函数的全局变量(概念比较狭义,方便理解Python中的闭包)
我们来看这样一个问题:

def add_tag(tag):
    def add_content(content):
        return "<%s> %s </%s>" % (tag, content, tag)

    return add_content

>>>a = add_tag("a")
>>>a(content="hello world")
<a> hello world </a>

tag这个变量对于外层函数add_tag来说只是一个局部变量,但对与内层函数add_content来说就是全局变量了,并且对封装函数中任何一个变量,外部都无法访问,这就形成了一个封闭的概念。

简单闭包装饰器
做一个输入检查的问题,验证name=admin,password=123456。当然如果对于多个函数接口都需要验证这样的参数,使用装饰器是最好的选择。

def login(name, password):
    if name != "admin" or password != "123456":
        return "username or password error"

    print "login success."
    print "do something."

使用装饰器以后,登陆函数中间不必做验证,只需要实现逻辑:

def check_login(func):
    def _wrapper(name, password):
        if name != "admin" or password != "123456":
            print "login failed"
            return "username or password error"
        return func(name, password)

    return _wrapper


@check_login
def login(name, password):
    print "login success."
    print "do something."

>>>login("admin", "123456")
login success.
do something.
>>>login("admin", "156")
login failed

注意观察外层接收函数对象,内层接受参数,那么装饰就是这样的

>>>check_login(login)("admin", "123456")
login success.
do something.
>>>check_login(login)("admin", "1256")
login failed

发现结果是一样的,check_login(login)返回的是内层函数对象,内层函数刚好接收用户名和密码两个参数,也就是说@这个语法糖只是简化了展开式。

思考
仔细思考会发现,这里有问题,这个登陆检测装饰器只接受两个参数,如何支持任意多个参数呢,这个问题还是留给读者吧。

为了加深理解,再加一个日志装饰器,输出调用函数的名称,两个装饰器一起装饰login函数。(此处有坑,小心)

def log(func):
    def _wrapper(*args, **kwargs):
        print "call %s " % func.__name__
        return func(*args, **kwargs)

    return _wrapper


def check_login(func):
    def _wrapper(name, password):
        if name != "admin" or password != "123456":
            print "login failed"
            return "username or password error"
        return func(name, password)

    return _wrapper


@log
@check_login
def login(name, password):
    print "login success."
    print "do something."


>>>login("admin", "156")
call _wrapper 
login failed

>>>login("admin", "123456")
call _wrapper 
login success.
do something.

装饰器的最外层函数接收的参数为函数名,这样函数名就作为内部的全局变量,在任何地方都可以调用了,内层函数接受函数的参数,主要的逻辑放在内层函数里面,比如添加日志信息,或者做参数检查等操作。最后就是按层级关系返回函数,但在这里还有一个问题,就是调用函数的名称发生了变化,因为外层函数是由_wrapper返回的,因此最终得到的函数签名就变成了_wrapper
只需要导入functools下的wraps,然后装饰在内层函数上就能保证函数签名的了。

import functools

def log(func):
    @functools.wraps(func)
    def _wrapper(*args, **kwargs):
        print "call %s " % func.__name__
        return func(*args, **kwargs)

    return _wrapper

给装饰器添加参数
不仅需要输出函数名的调用,还需要指定日志的级别,比如我们装饰一个函数@log(info),这个函数就得输出[info] call login。只需要在最外层改变接受参数,次内层接受函数名,内层接受函数的参数,也就是加了三层,看起来比较复杂了。

import functools

def log(level):
    def _decorator(func):
        @functools.wraps(func)
        def _wrapper(*args, **kwargs):
            print "[%s] call %s " % (level, func.__name__)
            return func(*args, **kwargs)

        return _wrapper

    return _decorator

@log("info")
def login(name, password):
    print "login success."
    print "do something."


>>>login("admin", "123456")
[info] call login 
login success.
do something.

对于三层的装饰器,@语法糖相当于log("info")(login)("admin", "123456"),看着是不是有点蒙了,(我刚开始学习的时候也是抓狂的,所以推荐后面两种),理解以后用哪种就看个人口味了。给出部分代码如下:

def login(name, password):   
    print "login success."   
    print "do something."

>>>log("info")(login)("admin", "123456")
[info] call login 
login success.
do something.

2. 类装饰器

最麻烦的闭包装饰器解决了,类装饰器写起来容易,并且可读性好多了,废话不多说,直接实现上面日志例子吧。

class Log(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print "call %s" % self.func.__name__
        return self.func(*args, **kwargs)

@Log
def login(name, password):
    print name, password

>>> login("admin", "123456")
call login
admin 123456

初始化返回的对象必须是可调用的,只需要改变类的__call__属性,代码是不是易懂多了。实现带参数装饰器,也就是添加日志的级别的装饰器,稍稍麻烦一点:

class Log(object):
    def __init__(self, level):
        self.level = level

    def __call__(self, func):
        def _wrapper(*args, **kwargs):
            print "[%s] call %s" % (self.level, func.__name__)
            return func(*args, **kwargs)

        return _wrapper

@Log("debug")
def login(name, password):
    print name, password

>>> login("admin", "123456")
[debug] call login
admin 123456

3.第三方包实现的装饰器

wrapt这个包封装了装饰器,直接使用@wrapt.decorator装饰在函数上即可,对于无参数装饰器,实现很简单了,可读性就不用多说了,毕竟是第三方的包。

import wrapt

@wrapt.decorator
def log(func, instance, args, kwargs):
    print "call %s" % func.__name__
    return func(*args, **kwargs)

@log
def login(name, password):
    print name, password


>>>login("admin", "123456")
call login
admin 123456

有参数的情况,还需要在外面加一层函数,这层函数用来接收参数。

import wrapt

def log(level):
    @wrapt.decorator
    def wrapper(func, instance, args, kwargs):
        print "[%s]: call %s" % (level, func.__name__)
        return func(*args, **kwargs)

    return wrapper

@Log("debug")
def login(name, password):
    print name, password


>>>login("admin", "123456")
[debug] call login
admin 123456

小结

装饰器非常重要,刚开始学习都是很困难的。当然如果你要绕过困难,不学也可以实现相同的功能,只是你的代码会很糟糕,可读性差,代码复用率低。Python就是简洁优雅的,你非要写出冗余代码,真心浪费了Guido的一片苦心,一个上午就写了这么点文章,后期还会继续完善。

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

推荐阅读更多精彩内容

  • 1、装饰器原理 2、装饰器语法 3、装饰器执行的时间 装饰器在Python解释器执行的时候,就会进行自动装饰,并不...
    HopCoder阅读 1,187评论 0 0
  • 在学习 Python 的时候,庆幸自己有 JavaScript 的基础,在学习过程中,发现许多相似的地方,如导包的...
    柏丘君阅读 1,191评论 2 8
  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 1,447评论 0 6
  • 一.函数装饰器 1.从Python内层函数说起 首先我们来探讨一下这篇文章所讲的内容Inner Functions...
    软体动物Ai阅读 3,237评论 0 14
  • 2017年高考刚刚落幕,对浙江29万余名考生来说,相比高考作文,语文试卷的一套阅读理解风头更盛。今年浙江高考语文卷...
    凯美餐饮阅读 598评论 0 0