python 装饰器实现_Python装饰器系列01 - 如何正确地实现装饰器

虽然人们能利用函数闭包(function clouser)写出简单的装饰器,但其可用范围常受限制。多数实现装饰器的基本方式会破坏与内省(Introspection)的关联性。

可大多数人会说:who cares!

但我仍坚持追求正确地写出漂亮代码。

我爱内省(introspection),讨厌猴子补丁(Monkey Patching)

请记住以下两点:

要为被装饰器包裹的函数(wrapped function)保留内省功能。

要理解清楚Python对象模型的执行方式如何工作。

接下来,我会通过14篇blog来向你解释:

你的典型Python装饰器及包裹的函数哪里有问题

如何修复这些问题

以下是第一篇内容,我会从几个方面简单说明你的典型Python装饰器如何产生问题。

Python 装饰器基础知识

人皆所知Python装饰器语法如下:

@function_wrapper

def function():

pass

@符号为自Python2.4引入的装饰器的语法糖(syntactic sugar), 它等同以下写法

def function():

pass

function = function_wrapper(function)

此@装饰器语法用于包裹定义或修改的函数

装饰器与猴子补丁不同,前者作用于定义时,后者作用于运行时

函数wrapper剖析

以下用class来实现一个装饰器

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

以上例子,class实例初始化后会在其内部记录一个原函数(self.wrapped = wrapped),在调用这个被class装饰器包裹起来的函数时,实际上是通过调用class对象的__call()__方法来调用原函数。

你可以通过装饰器,在调用原函数之前或之后,实现一些额外的功能。如需修改传递给原函数的输入参数,或原函数返回的结果,你只要在__call__()方法内进行修改。

用class来实现装饰器或许不太流行(2014年)。普遍用函数闭包来实现装饰器。函数闭包实现方式为:利用嵌套函数逐层返回传入的原函数(wrapped)。代码如下:

def function_wrapper(wrapped):

def _wrapper(*args, **kwargs):

return wrapped(*args, **kwargs)

return _wrapper

@function_wrapper

def function():

pass

此例中,无明显地给内嵌函数_wrapper传入原函数wrapped,内嵌函数仍可通过外层函数function_wrapper的参数访问到原函数(闭包原理),与用class实现装饰器相比,此做法方便多了。

Introspecting a function

函数内省

我们期望函数可指定一些与描述自身相关的特性(properties),如__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

若以class方式实现的wrapper,类实例通常不带有__name__属性,以此方式去尝试访问原函数的name属性时会得到一个AttributeError异常

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

>>> print(function.__name__)

Traceback (most recent call last):

File "", line 1, in

AttributeError: 'function_wrapper' object has no attribute '__name__'

当以函数闭包方式实现装饰器時,为保留原函数相关信息,我们可以把原函数的相关属性Copy一份给内嵌函数。如下例,可正确获得原函数的__name__及__doc__内容。

def function_wrapper(wrapped):

def _wrapper(*args, **kwargs):

return wrapped(*args, **kwargs)

_wrapper.__name__ = wrapped.__name__

_wrapper.__doc__ = wrapped.__doc__

return _wrapper

@function_wrapper

def function():

pass

>>> print(function.__name__)

function

这样Copy属性实在费力,将来如有要追加的属性还得更新代码。例如我们想Copy__module__,还有Python 3新增加的__qualname__及__annotations__属性。我们可以利用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

如以class方式实现装饰器,则可用functools.update_wrapper(),如下例所示:

import functools

class function_wrapper(object):

def __init__(self, wrapped):

self.wrapped = wrapped

functools.update_wrapper(self, wrapped)

def __call__(self, *args, **kwargs):

return self.wrapped(*args, **kwargs)

虽然functools.wraps()能解决诸如访问原函数的__name__及__doc__的问题,但实际上并没有完美解决函数内省,接下来你会看到。

当我们查询被装饰器包裹的原函数的参数定义时,返回的结果却是wrapper的参数定义。以函数闭包实现的装饰器为例,返回的为内嵌函数的参数定义。因此,装饰器不具签名保护(not signature preserving)

import inspect

def function_wrapper(wrapped):

def _wrapper(*arg, **kwarg):

return wrapped(*arg, **kwarg)

return _wrapper

@function_wrapper

def function(arg1, arg2): pass

>>> print(inspect.signature(function))

(*arg, **kwarg)

以class实现的装饰器也是同样的结果。

import inspect

class function_wrapper:

def __init__(self, wrapped):

self.wrapped = wrapped

def __call__(self, *arg, **kwarg):

return self.wrapped(*arg, **kwarg)

@function_wrapper

def function(arg1, arg2): pass

>>> print(inspect.signature(function))

(*arg, **kwarg)

另一个和内省相关的例子是,当用inspect.getsource()尝试返回函数(此函数被以class方式实现的装饰器包裹起来)的源码时,会得到一个TypeError异常。

TypeError: <__main__.function_wrapper object at> is not a module, class, method,

function, traceback, frame, or code object

The terminal process terminated with exit code: 1

包裹class方法

和普通函数一样,装饰器也可应用在class的方法上。Python内置的两个特殊装饰器——@staticmethod和@classmethod可将普通的实例方法(instance method)转化为class相关的特殊方法。虽然这些特殊方法也隐含着一些问题。

class Class(object):

@function_wrapper

def method(self):

pass

@classmethod

def cmethod(cls):

pass

@staticmethod

def smethod():

pass

首先,就算在你的装饰器里用上了 functools.wraps() 或 functools.update_wrapper(),当你把这个装饰器放在 @classmethod 或 @staticmethod前面时,依然会得到一个异常。这是因为依然有一些属性并未被functools.wraps()或functools.update_wrapper()Copy进来。以下为Python2的运行情况。

class Class(object):

@function_wrapper

@classmethod

def cmethod(cls):

pass

Traceback (most recent call last):

File "", line 1, in

File "", line 3, in Class

File "", 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__'

此为Python2的bug所致,此bug已在Python3中得到修正。

就算在Python3中运行,依然有异常抛出。那是因为两个包裹类型(wrapper types,即@function_wrapper及@classmethod)都期望被包裹函数(wrapped function)是可以被直接调用的(callable)。此被包裹的函数可称之为描述器(descriptor)。这意味为了返回一个可调用的描述器,它(描述器)须先正确地与实例绑定起来。参考以下代码

class Class(object):

@function_wrapper

@classmethod

def cmethod(cls):

pass

>>> Class.cmethod()

Traceback (most recent call last):

File "classmethod.py", line 15, in

Class.cmethod()

File "classmethod.py", line 6, in _wrapper

return wrapped(*args, **kwargs)

TypeError: 'classmethod' object is not callable

简单并非意味着正确

虽然我们可以简单地实现装饰器,并不见得这些装饰器必然正确及长久有效。

至此,比较突出的问题如下:

保留函数的 __name__ and __doc__。

保留函数的参数定义。

保留获取函数源码的能力。

能够在带有描述器协议的其他装饰器上应用自己所写的装饰器。

functools.wraps() 为我们解决了第一个问题,但不能一劳永逸。例如不能解决内省相关的问题。

就算能解决内省相关的问题,简单实现的装饰器依然会破坏python对象的执行模型,譬如被装饰器包裹着的带描述器协议的对象。

第三方包(packages)如decorator模块尝试解决这些问题,但只能解决前面两点问题。通用猴子补丁动态地应用函数包装器(function wrapper)时依然会发生问题。

我们找出了一些问题,后续博文中,我们会看到如何解决这些问题。而且你也会写出优雅的装饰器。

请继续关注我下期博文,希望我能保持继续写博的冲劲。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/296954.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高校教师抄袭豆瓣博主文章,学校证实:基本属实!记过并调离教学科研岗位...

全世界只有3.14 % 的人关注了爆炸吧知识导读作为高层次人才被杭州市引进&#xff0c;并于杭州师范大学任教的教师郭某某最近陷入了一则被指博士论文涉嫌抄袭豆瓣网友事件&#xff0c;近日&#xff0c;学校调查后证实。本文来源&#xff1a;募格学术综合自&#xff1a;红星新闻 …

linux默认归档目录,Linux系统管理(第4章:目录和文件管理二)

Linux系统管理(第4章&#xff1a;目录和文件管理二)一.练习文件查看及检索操作1.查看/etc/filesystems文件&#xff0c;确认当前系统支持的文件系统类型Cat&#xff1a;用于连接多个文件的内容&#xff0c;更多用于查看文件内容2.分页查看/etc/services文件&#xff0c;了解各种…

ASP.NET Core Filter与IOC的羁绊

前言我们在使用ASP.NET Core进行服务端应用开发的时候&#xff0c;或多或少都会涉及到使用Filter的场景。Filter简单来说是Action的拦截器&#xff0c;它可以在Action执行之前或者之后对请求信息进行处理。我们知道.Net Core默认是提供了IOC的功能&#xff0c;而且IOC是.Net Co…

error: storage class specified for parameter问题

error: storage class specified for parameter今天编译时&#xff0c;出现error: storage class specified for parameter问题一下子出现了很多错误&#xff1b;最后&#xff0c;发现是新增的头文件中&#xff0c;最后一个函数的声明缺少一个";“ 引起这个错误虽然很小&a…

sql里如何for循环

declare i intset i1while i<1000begininsert into dbo.T_Uservalues(aaaa,ddddd)set ii1end转载于:https://www.cnblogs.com/lilyzhang/archive/2012/06/14/2549762.html

HTML meta refresh 刷新与跳转(重定向)页面

refresh 属性值 -- 刷新与跳转(重定向)页面refresh用于刷新与跳转(重定向)页面refresh出现在http-equiv属性中&#xff0c;使用content属性表示刷新或跳转的开始时间与跳转的网址引用网址: http://blog.csdn.net/jiankunking demo&#xff1a; <!DOCTYPE html> <html …

一组动图看懂3D打印原理

全世界只有3.14 % 的人关注了爆炸吧知识3D打印是制造业领域的一项新兴技术&#xff0c;被称为“具有工业革命意义的制造技术”。近年来&#xff0c;随着工业技术的进步&#xff0c;3D打印技术得到迅速发展并得到媒体的广泛关注&#xff0c;各类3D打印技术被纷纷报道。下面&…

生产者消费者_【线程通信】生产者消费者模型

1生产者消费者模型介绍生产者消费者模型&#xff0c;是每一个学习多线程的的人都需要知道的模型; 大致情况就是&#xff1a;有两个线程&#xff0c;一个负责生产产品&#xff0c;一个消费产品&#xff0c;两者公用同一块内存区域&#xff0c;也就是产品放在了同一块内存上面&am…

linux检查系统硬件信息命令,Linux查看系统信息(硬件信息, 系统设置信息等) 命令 | Soo Smart!...

Linux 查看系统信息命令是linux初学者必备的基础知识, 这些命令也非常有用, 因为进入linux第一件事就可能是首先查看系统信息, 因此必要的系统的学习一下这些linux系统信息命令还是非常有必要的!下面给除了各linux发行版比较常用的系统信息查询的命令, 大家可以参考, 同时也可以…

在Orchard中使用Image Gallery模块

作为ASP.NET MVC领域一款优秀的开源CMS&#xff0c;Orchard值得所有.NET Web开发人员学习和研究&#xff0c;然后二次开发&#xff0c;最后在其基础上创新。也是遵循国内人员学习IT技术的路线&#xff1a;引进->吸收->消化。 Orchard有很多优秀的功能&#xff0c;在此不一…

宝宝的成长脚印6/15

这半个多月来 在六一儿童节这天&#xff0c;中午带宝宝去一家杂货店门口坐了几分钟喜洋洋摇摇车&#xff0c;傍晚时带他去公园玩滑梯&#xff0c;坐转马&#xff0c;晚上请他吃火车头盒饭 小家伙自己拿勺吃饭越来越稳了&#xff0c;但还需要喂&#xff0c;有时喂他吃时会吃一两…

Dapr + .NET 实战(十二)服务调用之GRPC

欢迎大家参加4小时Dapr.NET 5的实战课程课程链接 https://ke.qq.com/course/4000292?tuin1271860f什么是GRPCgRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架。gRPC 的主要优点是&#xff1a;高性能轻量级 RPC 框架。协定优先 API 开发&#xff0c;默认使用协议缓冲…

SCVMM 2012 R2运维管理九之:添加非信任的Hyper-v主机和群集

SCVMM 2012 R2运维管理九之&#xff1a;添加非信任的Hyper-v主机和群集之前的博文中我们为各位朋友介绍了如何向SCVMM中添加受信任的Hyper-v的主机和群集&#xff0c;相信大家也都已经有了一个比较深刻的理解&#xff0c;那么今天的博文中就来和大家聊聊:如何添加非受信任的Hyp…

走访近20家代工厂后:近千块的大牌T恤,成本只要几十块

▲ 点击查看“很多人都知道大牌有溢价但是不知道大牌溢价逼近900%打个比方一件1000块的T恤&#xff0c;T恤成本如果是100那么剩下的900&#xff0c;算是买了个大牌logo”这是我们上个月去到的一家中国代工厂工厂内部人员向我们透露的他说&#xff0c;其实无论是纪梵希、爱马仕、…

python execute_command err_Python management.execute_from_command_line方法代碼示例

本文整理匯總了Python中django.core.management.execute_from_command_line方法的典型用法代碼示例。如果您正苦於以下問題&#xff1a;Python management.execute_from_command_line方法的具體用法&#xff1f;Python management.execute_from_command_line怎麽用&#xff1f;…

linux脚本多线程,Shell多线程操作及线程数控制实例

前言在业务开发过程中&#xff0c;经常会在后台写一些shell脚本处理数据&#xff0c;但估计很多人不知道shell脚本也可以支持多线程&#xff0c;而且非常简单。本篇文章主要就是介绍shell实现多进程以及进程数量控制。需求为了更好的说明问题&#xff0c;我们结合例子讲解&…

Node.js Undocumented(2)

写这种系列blog&#xff0c;是为了监督自己&#xff0c;不然我估计我不会有动力写完。这一节&#xff0c;我将介绍下Buffer这个module。js本身对文本友好&#xff0c;但是处理二进制数据就不是特别方便&#xff0c;因此node.js提供了Buffer模块来帮助你处理二进制数据&#xff…

注册asp.net 4.0 到iis

如果没有按照正常的先装iis后装.net的顺序&#xff0c;可以使用此命令重新注册一下&#xff1a; 32位的Windows&#xff1a;---------------------------------------------------------------------------1. 运行->cmd 2. cd C:\Windows\Microsoft.NET\Framework\v4.0.30…

公开征集 | 每个人都可以成为 COSCon'21 主论坛的开源明星

“ 点击蓝字 / 关注我们 ”| 作者&#xff1a;COSCon21 组委会| 编辑&#xff1a;钱奕| 设计&#xff1a;朱亿钦| 责编&#xff1a;沈于蓝在开源的世界里&#xff0c;我们常常说 Community &#xff1e; Code&#xff08;社区重于代码&#xff09;&#xff0c;Community 一词在…

python怎么处理异常然后继续_Python异常处理-返回行,继续

通常,在try块内触发异常后,您永远都无法将执行返回到特定的代码段,因为该异常可能在很深的某个地方发生,并受其他线程的其他副作用影响的许多其他状态.尽管您的程序可能不是这种情况,但是鉴于没有通用的解决方案允许在Python中进行此操作,因此使用异常处理基本上不可能实现您想…