Python - 装饰器使用过程中的误区
曾灵敏 — APRIL 27, 2015
装饰器基本概念
大家都知道装饰器是一个很著名的设计模式,经常被用于AOP(面向切面编程)的场景,较为经典的有插入日志,性能测试,事务处理, Web权限校验
, Cache
等。
Python语言本身提供了装饰器语法(@),典型的装饰器实现如下:
@function_wrapper
def function():
pass
@实际上是python2.4才提出的语法糖,针对python2.4以前的版本有另一种等价的实现:
def function():
pass
function = function_wrapper(function)
装饰器的两种实现
函数包装器 - 经典实现
def function_wrapper(wrapped):
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
return _wrapper
@function_wrapper
def function():
pass
类包装器 - 易于理解
class function_wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __call__(self, *args, **kwargs):
return self.wrapped(*args, **kwargs)
@function_wrapper
def function():
pass
函数(function)自省
当我们谈到一个函数时,通常希望这个函数的属性像其文档上描述的那样,是被明确定义的,例如 __name__
和 __doc__
。
针对某个函数应用装饰器时,这个函数的属性就会发生变化,但这并不是我们所期望的。
def function_wrapper(wrapped):
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
return _wrapper
@function_wrapper
def function():
pass
>>> print(function.__name__)
_wrapper
python标准库提供了 functools.wraps()
,来解决这个问题。
import functools
def function_wrapper(wrapped):
@functools.wraps(wrapped)
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
return _wrapper
@function_wrapper
def function():
pass
>>> print(function.__name__)
function
然而,当我们想要获取被包装函数的参数( argument
)或源代码( source code
)时,同样不能得到我们想要的结果。
import inspect
def function_wrapper(wrapped): ...
@function_wrapper
def function(arg1, arg2): pass
>>> print(inspect.getargspec(function))
ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)
>>> print(inspect.getsource(function))
@functools.wraps(wrapped)
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
包装类方法(@classmethod)
当包装器( @function_wrapper
)被应用于@classmethod
时,将会抛出如下异常:
class Class(object):
@function_wrapper
@classmethod
def cmethod(cls):
pass
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in Class
File "<stdin>", line 2, in wrapper
File ".../functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'classmethod' object has no attribute '__module__'
因为 @classmethod
在实现时,缺少 functools.update_wrapper
需要的某些属性。这是 functools.update_wrapper
在python2中的bug,3.2版本已被修复,参考http://bugs.python.org/issue3445。
然而,在python3下执行,另一个问题出现了:
class Class(object):
@function_wrapper
@classmethod
def cmethod(cls):
pass
>>> Class.cmethod()
Traceback (most recent call last):
File "classmethod.py", line 15, in <module>
Class.cmethod()
File "classmethod.py", line 6, in _wrapper
return wrapped(*args, **kwargs)
TypeError: 'classmethod' object is not callable
这是因为包装器认定被包装的函数( @classmethod
)是可以直接被调用的,但事实并不一定是这样的。被包装的函数实际上可能是描述符( descriptor
),意味着为了使其可调用,该函数(描述符)必须被正确地绑定到某个实例上。关于描述符的定义,可以参考https://docs.python.org/2/howto/descriptor.html。
总结 - 简单并不意味着正确
尽管大家实现装饰器所用的方法通常都很简单,但这并不意味着它们一定是正确的并且始终能正常工作。
如同上面我们所看到的, functools.wraps()
可以帮我们解决 __name__
和 __doc__
的问题,但对于获取函数的参数(argument)或源代码( source code
)则束手无策。
以上问题,wrapt都可以帮忙解决,详细用法可参考其官方文档:http://wrapt.readthedocs.org
本文作者系OneAPM工程师曾灵敏 ,想阅读更多好的技术文章,请访问OneAPM官方技术博客。
Python - 装饰器使用过程中的误区的更多相关文章
- python装饰器(基础中的重点)
一.简单的装饰器 1.为什么要使用装饰器呢? 装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展 装饰器的本质:就是一个闭包函数 那么我们先来看一个简单的装饰器:实现计算每个函数的执 ...
- python 装饰器的坑
今天研究了下装饰器,添加重试功能遇到了个坑,跟大家分享一下: 代码如下: def re_try(maxtry): print locals() def wrapper(fn): print local ...
- Python装饰器的调用过程
在Python学习的过程中,装饰器是比较难理解的一个应用.本人也在学习期间也遇到很多坑,现将装饰器的基本调用过程总结一下. 首先,装饰器用到了“闭包”,而“闭包”是学习装饰器的基础,所以在讲装饰器之前 ...
- Python 装饰器装饰类中的方法
title: Python 装饰器装饰类中的方法 comments: true date: 2017-04-17 20:44:31 tags: ['Python', 'Decorate'] categ ...
- python装饰器中@wraps作用--修复被装饰后的函数名等属性的改变
Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的de ...
- python装饰器在接口自动化测试中的应用
在讲解装饰器在接口自动化测试项目的应用之前,我们先来介绍一下python装饰器到底是个什么 装饰器 说装饰器就不得不提一下函数这个一等公民了,在python中函数有几个特性先来了解一下 函数的一些特性 ...
- python装饰器通俗易懂的解释!
1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...
- Python装饰器由浅入深
装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...
- python 装饰器、内部函数、闭包简单理解
python内部函数.闭包共同之处在于都是以函数作为参数传递到函数,不同之处在于返回与调用有所区别. 1.python内部函数 python内部函数示例: def test(*args): def a ...
随机推荐
- 网站网页生成.shtml访问无法显示
网站换了服务器后发现shtml网页无法访问,原因是没有注册.shtml扩展名,解决方法如下 IIS6.0解析shtm,shtml文件由于IIS6.0的安全性较以前有特别大的改进,所以在很多功能默认情况 ...
- jQuery选项卡插件
html结构 <ul id="tabs" class="tabs"> <li data-tab="users">Us ...
- meteor 实现 微信分享
Template.hello.events({ 'click button': function () { // increment the counter when button is clicke ...
- 关于Swift中实现Lazy initialize的方式
在oc中我们通过 -(CardMatchingGame *)game { if(!_game) _game=[[CardMatchingGame alloc] initWithCardCount:[s ...
- Java实现UDP之Echo客户端和服务端
Java实现UDP之Echo客户端和服务端 代码内容 采用UDP协议编写服务器端代码(端口任意) 编写客户机的代码访问该端口 客户机按行输入 服务器将收到的字符流和接收到的时间输出在服务器consol ...
- LC_ALL=C
设置LC_ALL=C,可以让sort按照字节排序;
- ArcGIS Server10.2服务启动不了之http://localhost:6080/arcgis/manager无法打开之arcMap 无法打开6080admin问题解决之路
遇到的问题:在services.msc中可以正常启动arcGIS server ,但是过几秒种服务就自动关闭, 而且manager打不开, 各种方法都试过了,什么关闭杀毒软件,更改服务配置文件,更改a ...
- python3.4 安装ipython notebook
1.安装python3.4 2.安装pyreadline 3.安装ipython-1.2.1.zip 4.安装pyzmq-14.7.0-cp34-none-win32.whl,ZIP包有问题,下载wh ...
- JAVA类与对象(七)------继承
理解:继承可以理解为一个对象获取属性的过程.如果类A是类B的父类,而类B是类C的父类,我们也称C是A的子类,类C是从类A继承而来. 在java中,类的继承是单一继承,也就是说,一个子类只能拥有一 ...
- 启动级别:init 0,1,2,3,4,5,6
这是个很久的知识点了,只是自己一直都迷迷糊糊的,今天在翻出来好好理解下.. 0:停机 1:单用户形式,只root进行维护 2:多用户,不能使用net file system 3:完全多用户 5:图形化 ...