装饰器 / 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. 《Cracking the Coding Interview》——第4章:树和图——题目5

    2014-03-19 04:11 题目:设计算法检查一棵二叉树是否为二叉搜索树. 解法:既然是二叉搜索树,也就是说左子树所有节点都小于根,右子树所有节点都大于根.如果你真的全都检查的话,那就做了很多重 ...

  2. Python全栈工程师(数值类型、运算符)

    ParisGabriel       Python 入门基础     python的应用领域: 1.系统运维 2.网络编程(如:网络爬虫,搜索引擎,服务器编程) 3.科学计算 4.航空领域(如:卫星, ...

  3. iterm2+vim使用

    iterm2+vim 终端切换为iterm2+zsh+oh my zsh,确实好用. I term2常用快捷键记录 新建标签:cmd+t 关闭标签:cmd+w 切换标签:cmd+数字, 切换全屏:cm ...

  4. 编译caffe遇到的问题

    1. failed to see hdf5.h https://askubuntu.com/questions/629654/building-caffe-failed-to-see-hdf5-h 2 ...

  5. golang中从一个日期开始往后增加一段时间

    废话少说上code, 这个是从当前日期开始,往后增加一个月时间 package main import ( "fmt" "time" ) func main() ...

  6. fragment中的WebView返回上一页

    public final class Text1Fm extends Fragment { static WebView mWeb; private View mContentView; privat ...

  7. jQuery基础知识点(下)

    在实际开发中,jQuery的实践性非常强大.上一篇本人已经整理出了一部分基础知识点,该文即是对以上的补充和扩展. 1.表单值的操作 //获取文本框的值 //txt.value var val = $( ...

  8. IIS7 无法显示 htm js 图片 css的问题

    中文:开始->控件面板->程序->打开或关闭windows功能->Internet信息服务->万维网服务->常见http功能->静态内容(选中) englis ...

  9. [洛谷P3810]【模板】三维偏序(陌上花开)

    题目大意:有$n$个元素,第$i$个元素有三个属性$a_i,b_i,c_i$,设$f(i)=\sum\limits_{i\not = j}[a_j\leqslant a_i,b_j\leqslant ...

  10. BZOJ3456 城市规划 【分治NTT】

    题目链接 BZOJ3456 题解 据说这题是多项式求逆 我太弱不会QAQ,只能\(O(nlog^2n)\)分治\(NTT\) 设\(f[i]\)表示\(i\)个节点的简单无向连通图的数量 考虑转移,直 ...