Python的程序结构[8] -> 装饰器/Decorator -> 装饰器浅析
装饰器 / Decorator
目录
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 -> 装饰器浅析的更多相关文章
- Python的程序结构[7] -> 生成器/Generator -> 生成器浅析
生成器 / Generator 目录 关于生成器 生成器与迭代器 生成器的建立 通过迭代生成器获取值 生成器的 close 方法 生成器的 send 方法 生成器的 throw 方法 空生成器的检测方 ...
- Python的程序结构[1] -> 方法/Method[1] -> 静态方法、类方法和属性方法
静态方法.类方法和属性方法 在 Python 中有三种常用的方法装饰器,可以使普通的类实例方法变成带有特殊功能的方法,分别是静态方法.类方法和属性方法. 静态方法 / Static Method 在 ...
- Python基本程序结构
条件判断: 计算机之所以能做很多自动化的任务,因为它可以自己做条件判断.比如,输入用户年龄,根据年龄打印不同的内容,在Python程序中,用if语句实现:
- Python的程序结构[1] -> 方法/Method[0] -> 类实例方法、私有方法和抽象方法
类实例方法.私有方法和抽象方法 Python中最常用的就是类实例方法,类似于属性中的类实例属性,同时,也存在与私有属性类似方法,即私有方法,下面介绍这两种常见的方法,以及一种特殊意义的类实例方法 -- ...
- Python的程序结构[1] -> 方法/Method[2] -> 魔术方法 __init__ / __del__ / __new__
魔术方法 / Magic Method 魔法方法就是可以给你的类增加魔力的特殊方法(实质应称为特殊方法,魔术方法在JavaScript中有所体现,对象具有不透明特性,而且无法在自定义对象中模拟这些行为 ...
- Python入门-程序结构扩展
deque双端队列 #双端队列,就是生产消费者模式,依赖collections模块 from collections import deque def main(): info = deque((&q ...
- Python的程序结构[0] -> 属性/Property[0] -> 类属性、实例属性和私有属性
类属性.实例属性和私有属性 Python中类的属性主要包括类属性,实例属性和私有属性,下面是对三种属性的简单介绍 类属性 / Class Property 类属性在__init__()之外初始化,在外 ...
- Python的程序结构[2] -> 类/Class[0] -> 类的特殊属性
类的特殊属性 / Special Property of Class Python 中通过 class 进行类的定义,类可以实例化成实例并利用实例对方法进行调用. 类中还包含的一些共有的特殊属性. 特 ...
- Python的程序结构[2] -> 类/Class[1] -> 基类与继承
基类与继承 / Base Class and Inheritance Class 面向对象的特性使得 Python 中不可避免地需要使用到类和类的继承,类的继承可以使得代码很好的被重用.下面以一些代码 ...
随机推荐
- springboot相关链接
springboot的三种启动方式 https://blog.csdn.net/my__Sun_/article/details/72866329 springboot学历历程 https://www ...
- python-isinstance,issubclass
1 #当存在继承关系时,两个类中存在相同的方法,如何执行父类的方法,通过super 2 class C1: 3 def f1(self): 4 print('c1.f1') 5 6 7 class C ...
- HDU 4714 Tree2cycle 找规律
假设最少删除的边的个数为cost,显然,最终答案即为cost+cost+1 (因为删除一条边,就会增加一个链,所以删除cost条边后,就会有cost+1条链,将这cost+1条链连接起来的代价为cos ...
- iOS CGContextRef 画一条直线,仅仅是画一条直线
今天周末休息,想好好补补课,无奈,弄了一上午,全部都是半边拉块的demo,有一种深深的挫败感. 中午睡醒一觉后,又看了一集“奔跑吧兄弟”,然后一下午时间就过去了. 仔细一想,应该是我的补课方法不对:要 ...
- SheetJS & Error: Sheet names cannot exceed 31 chars
SheetJS Error: Sheet names cannot exceed 31 chars title + version https://github.com/SheetJS/js-xlsx ...
- hdu 1534 Schedule Problem (差分约束)
Schedule Problem Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...
- [luogu3676] 小清新数据结构题 [树链剖分+线段树]
题面 传送门 思路 本来以为这道题可以LCT维护子树信息直接做的,后来发现这样会因为splay形态改变影响子树权值平方和,是splay本身的局限性导致的 所以只能另辟蹊径 首先,我们考虑询问点都在1的 ...
- [poj] 2286 The Rotation Game || ID-DFS
原题 有1234四个数字,每个数字八个.有八种方向的移动,使得操作后中间八个方块的数字相同,求最小操作步数. 对于这种求最小步数的看起来就是dfs的题,就ID-DFS就好了. //不知道为什么都是ID ...
- Lettcode Kth Largest Element in an Array
Lettcode Kth Largest Element in an Array 题意:在无序数组中,寻找第k大的数字,注意这里考虑是重复的. 一直只会简单的O(nlogn)的做法,听说这题有O(n) ...
- vs修改快捷键
https://jingyan.baidu.com/album/9158e0006e10d8a254122826.html?picindex=1 https://sanwen8.cn/p/114IrR ...