装饰器 / Decorator


目录

  1. 关于闭包
  2. 装饰器的本质
  3. 语法糖
  4. 装饰器传入参数

1 关于闭包 / About Closure


装饰器其本质是一个闭包函数,为此首先理解闭包的含义。

闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(Function Closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体,这种组合使用一种映射关系将函数的自由变量(在 local 使用但是在 enclosing scope 使用的变量)与函数相关联。

2 装饰器的本质 / Essence of Decorator


装饰器其实本质源于闭包的函数,这个闭包函数将一个函数作为参数传入,然后返回一个替代版的函数。

A decorator is a function that takes a function object as an argument, and returns a function object as a return value.

下面以一段代码为例进行解释

 # Enclosure Function and Decorator
def deco(func):
# This is a sbstitute returned to replace input function
def wrapper():
return func()+1
return wrapper def foo():
return 1 print(foo()) #
print(deco(foo)()) #

首先定义一个函数 deco,在 deco 函数内再定义一个 wrapper 函数,此时 deco 函数内部,wrapper 函数外部,就是 wrapper 的闭包范围,wrapper 会记录到环境内的关系,同时根据 LEGB 搜索原则找寻参数。因此在 return 的时候可以到 func 函数。

Note: 此处的 deco 函数的主要作用在于,对传入的 func 做一定的处理后,返回一个新的函数 wrapper 作为替代。

随后定义一个 foo 函数作为传入的参数,通过输出语句可以看到,经过 deco 函数装饰的 foo 在不改动内部函数的前提下,完成了对函数行为的改变。这样一个 deco 函数,就是装饰器的本质起源。

3 语法糖 / Syntactic Sugar


前面介绍的装饰器本质方法依旧存在一个麻烦需要处理,也就是当需要对所有的被装饰函数都进行相同的装饰时,需要在每个调用的位置都调用一次装饰函数,这样无疑增加了代码的修改难度。

为此语法糖@便出现了,利用语法糖可以很方便的装饰一个函数,只需要在被装饰的函数之上使用@ deco便可以将函数 deco 装饰到被装饰的函数 fox上,而当再次调用 fox 时,首先会调用一次 fox = deco(fox),自动完成对函数的装饰。

 # Use Syntactic Sugar to make Decorator
@ deco
def fox():
return 1
print(fox()) #

同时,语法糖也是可以叠加使用的,而调用的顺序与声明的顺序是相反的,即自下而上。

@ deco
@ deco
def fox():
return 1
print(fox()) #

4 装饰器传入参数 / Decorator Parameter Pass


装饰器的参数传入主要有两种,一是能够装饰需要传入参数的函数,二是装饰器自身参数。

首先介绍如何使装饰器装饰带参数传入的函数,最直接的方式是定义返回函数 wrapper 时,将其定义成与被装饰函数 func 相同的形参结构,这样返回的 wrapper 函数便能够接受 func 的参数传入。利用装饰器函数和语法糖分别进行验证,可以得到期望的结果。

 # Make a Decorator can receive function para
def decor(func):
def wrapper(a, b):
return func(a, b)+1
return wrapper def fun(a, b):
return a+b @ decor
def funx(a, b):
return a+b print(fun(1, 2)) #
print(decor(fun)(1, 2)) #
print(funx(1, 2)) #

但是上面的方法具有局限性,当我们不知道被装饰函数需要传入的参数数量时,便无法定义内部的返回函数 wrapper。同时,当被装饰函数的输入不同时,装饰器的通用性便被破坏。为此可以利用运算符 * 和 ** 来对任意参数进行元组或字典的解包,从而完成一个通用的装饰器。

 # Common function para Decorator
def decor(func):
def wrapper(*argv, **kwargv):
return func(*argv, **kwargv)+1
return wrapper @ decor
def fun(a, b, c=0):
return a+b+c @ decor
def funx(a, b, c, d, e=0):
return a+b+c+d+e print(fun(1, 2, c=3)) #
print(funx(1, 2, 3, 4, e=5)) #

同样的,对于装饰器,也可以传入一个参数,可利用这个参数完成诸如装饰器开关等操作(设置 True 和 False 来确定返回 _wrapper 还是原函数 func)。要实现这一功能,则需要在装饰器外再多一层函数,下面的 decora 函数实质上是起到返回装饰器函数 _décor 的作用,语法糖修饰的 @ decora(3) 实际上等价于 @ _decor,因为 decora(3) 返回的是一个函数 _decor。同时,在此处也体现了函数闭包的特性,即在内层函数中记录了闭包环境中的参数 c 值。

 # Decorator receive its own para
def decora(c):
def _decor(func):
def _wrapper(a, b):
return func(a, b)+c
return _wrapper
return _decor # Note: decora(3) return _decor
# @ decora(3) equals to @ _decor
# _decor record c value thanks to Closure
@ decora(3) # @ _decor
def fun(a, b):
return a+b print(fun(1, 2)) #

相关阅读


1. LEGB 搜索原则

参考链接


http://blog.csdn.net/slvher/article/details/39227815

Python的程序结构[8] -> 装饰器/Decorator -> 装饰器浅析的更多相关文章

  1. Python的程序结构[7] -> 生成器/Generator -> 生成器浅析

    生成器 / Generator 目录 关于生成器 生成器与迭代器 生成器的建立 通过迭代生成器获取值 生成器的 close 方法 生成器的 send 方法 生成器的 throw 方法 空生成器的检测方 ...

  2. Python的程序结构[1] -> 方法/Method[1] -> 静态方法、类方法和属性方法

    静态方法.类方法和属性方法 在 Python 中有三种常用的方法装饰器,可以使普通的类实例方法变成带有特殊功能的方法,分别是静态方法.类方法和属性方法. 静态方法 / Static Method 在 ...

  3. Python基本程序结构

    条件判断: 计算机之所以能做很多自动化的任务,因为它可以自己做条件判断.比如,输入用户年龄,根据年龄打印不同的内容,在Python程序中,用if语句实现:

  4. Python的程序结构[1] -> 方法/Method[0] -> 类实例方法、私有方法和抽象方法

    类实例方法.私有方法和抽象方法 Python中最常用的就是类实例方法,类似于属性中的类实例属性,同时,也存在与私有属性类似方法,即私有方法,下面介绍这两种常见的方法,以及一种特殊意义的类实例方法 -- ...

  5. Python的程序结构[1] -> 方法/Method[2] -> 魔术方法 __init__ / __del__ / __new__

    魔术方法 / Magic Method 魔法方法就是可以给你的类增加魔力的特殊方法(实质应称为特殊方法,魔术方法在JavaScript中有所体现,对象具有不透明特性,而且无法在自定义对象中模拟这些行为 ...

  6. Python入门-程序结构扩展

    deque双端队列 #双端队列,就是生产消费者模式,依赖collections模块 from collections import deque def main(): info = deque((&q ...

  7. Python的程序结构[0] -> 属性/Property[0] -> 类属性、实例属性和私有属性

    类属性.实例属性和私有属性 Python中类的属性主要包括类属性,实例属性和私有属性,下面是对三种属性的简单介绍 类属性 / Class Property 类属性在__init__()之外初始化,在外 ...

  8. Python的程序结构[2] -> 类/Class[0] -> 类的特殊属性

    类的特殊属性 / Special Property of Class Python 中通过 class 进行类的定义,类可以实例化成实例并利用实例对方法进行调用. 类中还包含的一些共有的特殊属性. 特 ...

  9. Python的程序结构[2] -> 类/Class[1] -> 基类与继承

    基类与继承 / Base Class and Inheritance Class 面向对象的特性使得 Python 中不可避免地需要使用到类和类的继承,类的继承可以使得代码很好的被重用.下面以一些代码 ...

随机推荐

  1. CocosCreator设置模拟器默认横竖屏以及机型

    之前好好的横屏,今天不知道为毛突然变成竖屏了,虽然可以在点击模拟器左上角进行设置,   但是 每次启动模拟器又变成竖屏了,折腾了很久,终于找到了设置的地方,记录下:        

  2. glup自动化构建工具

    实现的功能包括 js压缩,css文件合并压缩并在html加版本号,压缩html文件 1.安装gulp 建议参考官网就行http://www.gulpjs.com.cn/docs/getting-sta ...

  3. python负数除法与模运算

    1.负数除法: >>> print 45/76>>> print -45/7-7 >>> print 45/-7-7 >>> p ...

  4. jquery validate 使用示范

    最近应公司要求做了一个jquery的示例文件,包括:模态窗口怎么实现:jquery validate下的校验:怎么做图片特效:怎么实现异步操作:实现图片上传剪切效果等很多特效: 这里把jquery校验 ...

  5. HDU 4665 Unshuffle DFS找一个可行解

    每层找一对相等的整数,分别放在两个不同的串中. 参考了学弟的解法,果断觉得自己老了…… #include <cstdio> #include <cstring> #includ ...

  6. 整合S2SH框架

    S2SH框架(Struts2,Spring,Hibernate)整合 Struts2.Hibernate和Spring.其中在Struts2部分主要为MVC设计思想,Struts2的处理流程及配置,S ...

  7. 五、vue nextTick

    主线程的执行过程就是一个 tick,而所有的异步结果都是通过 "任务队列" 来调度被调度. 消息队列中存放的是一个个的任务(task). 规范中规定 task 分为两大类,分别是 ...

  8. easyui中datagrid空数据集不刷新的解决方式

    datagrid空间可以异步请求json数据,并将新数据覆盖原有数据,重绘数据表. 但是当回来空数据集的时候,js会产生这样一条错误: TypeError: rows is null for(var ...

  9. CLion 的 Debug 模式是怎么回事

    我对这个问题产生兴趣的起因是 Codeoforces Avito Challenges 2018 的 E 题,我想到了正解,但写得太慢,最后一刻才提交.有个地方写错,结果是 Runtime error ...

  10. Gym100286C Clock

    不想打题面,题面戳这里. 被这题吓到了,感觉无从下手.最后还是看着题解和别人的代码加以改编最后写出了的.其实理解之后写出了也就是三四十行的样子了. 首先题目有个很重要的条件--转动某个针只会对周期比他 ...