题图来自Dobiasd的article项目聊到的Python的学习曲线,虽然作者说” The only purpose of this is to entertain. It has no empirical base whatsoever. “,但是我觉得还是需要重视文章中提到的一个说法:python掌握程度的评价标准中肯定包含装饰器。装饰器是python语言的语法糖,没有它并不会影响python编程,但作为评价标准之一,我想自己还是要能够回答下述几个问题:
1.Python装饰器有什么用?
2.Python装饰器是什么?
3.Python装饰器实现的原理是什么?
4.Python装饰器有哪几种具体形式?
5.Python装饰器有哪些应用场景?
6.Python装饰器在著名开源项目中的应用?
这就是这篇文章的由来。
1.Python装饰器有什么用?
我们假设你的程序实现了say_hello()
和say_goodbye()
两个函数。
def say_hello(): print "hello!" def say_goodbye(): print "hello!" # bug here if __name__ == '__main__': say_hello() say_goodbye()
但是在实际调用中,我们发现程序出错了,上面的代码打印了两个hello。经过调试你发现是say_goodbye()
出错了。老板要求调用每个方法前都要记录进入函数的名称,比如这样:
[DEBUG]: Enter say_hello()
Hello!
[DEBUG]: Enter say_goodbye()
Goodbye!
好,小A是个毕业生,他是这样实现的。
def say_hello(): print "[DEBUG]: enter say_hello()" print "hello!" def say_goodbye(): print "[DEBUG]: enter say_goodbye()" print "hello!" if __name__ == '__main__': say_hello() say_goodbye()
很low吧? 嗯是的。小B工作有一段时间了,他告诉小A可以这样写。
def debug(): import inspect caller_name = inspect.stack()[1][3] print "[DEBUG]: enter {}()".format(caller_name) def say_hello(): debug() print "hello!" def say_goodbye(): debug() print "goodbye!" if __name__ == '__main__': say_hello() say_goodbye()
是不是好一点?那当然,但是每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?那么装饰器这时候应该登场了。
2.Python装饰器是什么?
最早的时候Python 2.2版本引入为对象模型加入了static methods和class methods(早期的装饰器),而装饰器(Decorator)的概念是在Python 2.4版本作为新的语言特性正式引入Python的,Python 2.6版本对将函数装饰器扩展到类装饰器(同时包括了getter, setter and deleter)。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。
3.Python装饰器实现的原理是什么?
3.1闭包
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。用比较容易懂的人话说,就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包,一般支持函数式编程的语言都支持闭包。看例子:
def make_printer(msg): def printer(): print msg # 夹带私货(外部变量) return printer # 返回的是函数,带私货的函数 printer = make_printer('Foo!') printer()
闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。同一个的函数夹带了不同的私货,就实现了不同的功能。其实你也可以这么理解,闭包和面向接口编程的概念很像,可以把闭包理解成轻量级的接口封装。
def tag(tag_name): def add_tag(content): return "<{0}>{1}</{0}>".format(tag_name, content) return add_tag content = 'Hello' add_tag = tag('a') print add_tag(content) # <a>Hello</a> add_tag = tag('b') print add_tag(content) # <b>Hello</b>
在这个例子里,我们想要一个给content
加tag
的功能,但是具体的tag_name
是什么样子的要根据实际需求来定,对外部调用的接口已经确定,就是add_tag(content)
。如果按照面向接口方式实现,我们会先把add_tag
写成接口,指定其参数和返回类型,然后分别去实现a和b的add_tag
。
但是在闭包的概念中,add_tag
就是一个函数,它需要tag_name
和content
两个参数,只不过tag_name这个参数是打包带走的。所以一开始时就可以告诉我怎么打包,然后带走就行。
上面的例子不太生动,其实在我们生活和工作中,闭包的概念也很常见。比如说手机拨号,你只关心电话打给谁,而不会去纠结每个品牌的手机是怎么实现的,用到了哪些模块。再比如去餐馆吃饭,你只要付钱就可以享受到服务,你并不知道那桌饭菜用了多少地沟油。这些都可以看成闭包,返回来的是一些功能或者服务(打电话,用餐),但是这些功能使用了外部变量(天线,地沟油等等)。
你也可以把一个类实例看成闭包,当你在构造这个类时,使用了不同的参数,这些参数就是闭包里的包,这个类对外提供的方法就是闭包的功能。但是类远远大于闭包,因为闭包只是一个可以执行的函数,但是类实例则有可能提供很多方法。
下面让我们来了解一下闭包的包到底长什么样子。其实闭包函数相对与普通函数会多出一个__closure__
的属性,里面定义了一个元组用于存放所有的cell对象,每个cell
对象一一保存了这个闭包中所有的外部变量。
>>> def make_printer(msg1, msg2): def printer(): print msg1, msg2 return printer >>> printer = make_printer('Foo', 'Bar') # 形成闭包 >>> printer.__closure__ # 返回cell元组 (, ) >>> printer.__closure__[0].cell_contents # 第一个外部变量 'Foo' >>> printer.__closure__[1].cell_contents # 第二个外部变量 'Bar'
3.2装饰器接口
因为Python的装饰器是一个固定的函数接口形式。它要求你的装饰器函数(或装饰器类)必须接受一个函数并返回一个函数:
# how to define def wrapper(func1): # 接受一个callable对象 return func2 # 返回一个对象,一般为函数 # how to use def target_func(args): # 目标函数 pass # 调用方式一,直接包裹 result = wrapper(target_func)(args) # 调用方式二,使用@语法,等同于方式一 @wrapper def target_func(args): pass result = target_func()
那么如果你的装饰器如果带参数呢?那么你就需要在原来的装饰器上再包一层,用于接收这些参数。这些参数(私货)传递到内层的装饰器里后,闭包就形成了。所以说当你的装饰器需要自定义参数时,一般都会形成闭包。(类装饰器例外)
4.Python装饰器有哪几种具体形式?
# 1. simple decorator def debug(func): def wrapper(*args, **kwargs): print("[DEBUG]: enter {}()".format(func.__name__)) return func(*args, **kwargs) return wrapper @debug def say_hello(msg): print('hello {}!'.format(msg)) @debug def say_goodbye(msg): print('goodbye {}'.format(msg)) # 2. decorator with parameter def log(level): def wrapper(func): def inner_wrapper(*args, **kwargs): print('[{level}]: enter function {func}()'.format( level=level, func=func.__name__)) return func(*args, **kwargs) return inner_wrapper return wrapper @log(level='INFO') def say_hello_log(msg): print('logging\nhello {}'.format(msg)) @log(level='WARNING') def say_goodbye_log(msg): print('logging\ngoodbye {}'.format(msg)) # 3. simple class decorator class logs(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("[DEBUG]: enter func {}".format(self.func.__name__)) return self.func(*args, **kwargs) @logs def say_str(msg): print('hi {}, I am done!'.format(msg)) # 4. class decorator with parameter class logging(object): def __init__(self, level="INFO"): self.level = level def __call__(self, func): def wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level = self.level, func = func.__name__)) return func(*args, **kwargs) return wrapper @logging(level='ERROR') def say(msg): print("edony say {}!".format(msg)) # 5. inner decorator ## property class pro(object): def __init__(self, x): self._x = x def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I am a monkey") class pro2(object): def __init__(self, x): self._x = x @property def getx(self): 'I am a monkey' return self._x @getx.setter def setx(self, value): self._x = value @getx.deleter def delx(self): del self._x if __name__ == "__main__": say_hello('edony') say_goodbye('edony') say_hello_log('EDONY') say_goodbye_log('EDONY') say_str('bob') say("WTF, I made a mistake again!!!") pro_tmp = pro(1) print(pro_tmp.x) pro_tmp.x = 12.111 print(pro_tmp.x) help(pro.x) del(pro_tmp.x) pro2_tmp = pro2(2222) pro2_tmp.x = 12 print(pro2_tmp.x) help(pro2.getx)
5.Python装饰器有哪些应用场景?
Python官方的装饰器实例列表,你可以在里边看到装饰器的各种妙用PythonDecoratorLibrary
总结起来有这么几类(后续会有文章针对每种用法从源码进行详细的分析总结):
1. 注入参数(提供默认参数,生成参数)
2. 记录函数行为(日志、缓存、计时什么的)
3. 预处理/后处理(配置上下文什么的)
4. 修改调用时的上下文(线程异步或者并行,类方法)
6.Python装饰器在著名开源项目中的应用?
1.requests
[edony@edony requests-2.18.1:32]$ grep -rn "^\s\{0,\}@.*" `find . -name "*.py" -type f` | wc -l 97 [edony@edony requests-2.18.1:33]$ grep -rn "^\s\{0,\}@.*" `find . -name "*.py" -type f` ./requests/cookies.py:85: @property ./requests/cookies.py:89: @property ./requests/cookies.py:93: @property ./requests/models.py:61: @property ./requests/models.py:82: @staticmethod ./requests/models.py:109: @staticmethod ./requests/models.py:337: @staticmethod ./requests/models.py:689: @property ./requests/models.py:704: @property ./requests/models.py:711: @property ./requests/models.py:716: @property ./requests/models.py:721: @property ./requests/models.py:812: @property ./requests/models.py:832: @property
2.flask
[edony@edony flask-0.12.2:35]$ grep -rn "^\s\{0,\}@.*" `find . -name "*.py" -type f` | wc -l 422 [edony@edony flask-0.12.2:36]$ grep -rn "^\s\{0,\}@.*" `find . -name "*.py" -type f` ./examples/blueprintexample/simple_page/simple_page.py:7:@simple_page.route('/', defaults={'page': 'index'}) ./examples/blueprintexample/simple_page/simple_page.py:8:@simple_page.route('/<page>') ./examples/blueprintexample/test_blueprintexample.py:13:@pytest.fixture ./examples/flaskr/flaskr/flaskr.py:48:@app.cli.command('initdb') ./examples/flaskr/flaskr/flaskr.py:64:@app.teardown_appcontext ./examples/flaskr/flaskr/flaskr.py:71:@app.route('/') ./examples/flaskr/flaskr/flaskr.py:79:@app.route('/add', methods=['POST']) ./examples/flaskr/flaskr/flaskr.py:91:@app.route('/login', methods=['GET', 'POST']) ./examples/flaskr/flaskr/flaskr.py:106:@app.route('/logout') ./examples/flaskr/tests/test_flaskr.py:18:@pytest.fixture ./examples/jqueryexample/jqueryexample.py:15:@app.route('/_add_numbers') ./examples/jqueryexample/jqueryexample.py:23:@app.route('/') ./examples/minitwit/minitwit/minitwit.py:44:@app.teardown_appcontext ./examples/minitwit/minitwit/minitwit.py:60:@app.cli.command('initdb') ./examples/minitwit/minitwit/minitwit.py:92:@app.before_request