python-语法装饰器

python(转载)一周一个Python语法糖:(一)装饰器

作者:寒夏凉秋  原文链接:https://www.jianshu.com/p/fd746acbdf1e

Decorator

首先,我们来认识一下装饰器是什么: 装饰器是给现有的模块增添新的小功能 (在不改变原有模块功能的基础上)

假如我有个简单笔,它只能用一种颜色进行写字 我现在给它加上一只笔芯,它能换种颜色写字(又能换回来~) 这就是装饰器的朴素比喻

一、初探装饰器

手动写个装饰器吧

 1# 自定义装饰函数
 2
 3def decorator(fn):
 4    def wrapper(*args):
 5      #这里装饰器的作用是在函数调用前增加一句话表示装饰成功
 6
 7        print("this is decorator for %s" % fn.__name__)
 8        return fn(*args)
 9    return wrapper
10
11def hello(name):
12    print('hello,%s' %name)
13
14if __name__=="__main__":
15    #用赋值的形式进行装饰器
16    hello=decorator(hello)
17    hello("cool")

输出结果为:

1this is decorator for hello
2hello, cool

首先,我们要知道,在python中,函数也是一种对象(万物皆对象!)

  1. 函数可以赋值给一个变量(学过C语言的可以联想下函数指针)
  2. 函数可以定义在另一个函数内部

这也意味着一个函数可以返回另一个函数。

1hello=decorator(hello)

这一句代码中,将hello函数作为变量传入decorator装饰器中,然后hello方法decorator中的函数wrapper函数实现,同时包装新的功能,将新的函数wrapper作为变量返回 ,所以hello的新值是经过decorator装饰的wrapper新方法。

所以,装饰器装饰函数的时候,将函数作为变量传入装饰器内部,实际调用的是装饰器内部的函数(添加新功能之后的函数)

二、 @语法糖

Python 中装饰器语法并不用每次都用赋值语句。

在函数定义的时候就加上@+装饰器名字即可。

再来我们刚才的例子吧:

 1def decorator(fn):
 2    def wrapper(*args):
 3        print("this is decorator for %s" % fn.__name__)
 4        fn(*args)
 5    return wrapper
 6@decorator # @decorator <== hello=decorator(hello)
 7def hello(name):
 8    print('hello,%s' %name)
 9if __name__=="__main__":
10    hello("cool")

执行的效果和上面一样。

装饰器的顺序

比如我们有两个装饰器:

1@decorator_one
2@decorator_two
3def hello()
4    pass

这句代码实际上类似于:hello=decorator_one(decorator_two(hello))两个装饰器一层层地往外装饰

带参数的装饰器

我们说过,装饰器其实也是一种函数,所以它自身也是能带参数的

1@decorator(arg1, arg2)
2def func():
3    pass

类似于func = decorator(arg1,arg2)(func)。实际执行了两次函数(两个括号)。

来个实际点的例子吧: 我们手写html的时候需要各种补全(那个用编辑器的当然爽得飞起!)但是,如果是在python中用字符串去表示html标签的时候,就~坑爹了。总不能每个标签我都写一个方法吧

最方便的方法,写一个带参数的装饰器!

 1def makeHtmlTag(tag, *args, **kwargs):
 2    print(tag)
 3    def real_decorator(fn):
 4        css_class = " class='{0}'".format(kwargs["css_class"]) \
 5                                     if "css_class" in kwargs else ""
 6        print(kwargs["css_class"],fn.__name__)
 7        def wrapped(*args, **kwargs):
 8            return "<"+tag+css_class+">" + fn(*args, **kwargs) + "</"+tag+">"
 9        return wrapped
10    return real_decorator
11
12@makeHtmlTag(tag="b", css_class="bold_css")
13@makeHtmlTag(tag="i", css_class="italic_css")
14def hello():
15    return "hello world"
16
17print(hello())

运行结果:

1b
2i
3italic_css hello
4bold_css wrapped
5<b class='bold_css'><i class='italic_css'>hello world</i></b>

关于几点说明:

(1)在装饰器makeHtmlTag中,*args代表了参数元组,假如你传入的的参数分别是1,2,3,4,则args=(1,2,3,4);**kwds则参数字典,返回一个key为参数变量名,value为变量值的字典。这样我们就可以很方便地不用改动函数本身去获取函数传递的参数并进行装饰了

(2)使用装饰器的时候有个缺陷就是不能在过程中更改某个装饰器的参数值(比如该例子中 hello 的便签就永远是b,i了)

如果你觉得这样写太!麻!烦!了!什!么!鬼!

为什么我要在函数体中再定义一个函数体!!!!

难道还要我一层层剥开你的心吗?

用类的方式去写一个装饰器

 1class makeHtmlTagClass(object):
 2 
 3    def __init__(self, tag, css_class=""):
 4        self._tag = tag
 5        self._css_class = " class='{0}'".format(css_class) \
 6                                       if css_class !="" else ""
 7 
 8    def __call__(self, fn):
 9        def wrapped(*args, **kwargs):
10            return "<" + self._tag + self._css_class+">"  \
11                       + fn(*args, **kwargs) + "</" + self._tag + ">"
12        return wrapped
13 
14@makeHtmlTagClass(tag="b", css_class="bold_css")
15@makeHtmlTagClass(tag="i", css_class="italic_css")
16def hello(name):
17    return "Hello, {}".format(name)
18 
19print(hello("Hao Chen"))

关于说明:

  1. 我们将整个类作为一个装饰器,工作流程:
    1. 通过__init__()方法初始化类
    2. 通过__call__()方法调用真正的装饰方法
  2. 当装饰器有参数的时候,init() 成员就不能传入fn了,而fn是在call的时候传入的。(fn代表要装饰的函数)