装饰器

  实际上理解装饰器的作用很简单,在看core python相关章节的时候大概就是这种感觉。只是在实际应用的时候,发现自己很难靠直觉决定如何使用装饰器,特别是带参数的装饰器,于是摊开来思考了一番,写下一些心得。

装饰器简述

  为了完整起见,这里简要说明一下装饰器的语法。装饰器分为带参数得装饰器以及不带参数得装饰器。装饰器以及使用效果看起来大概是这样的。

#语法是这个样子的
@decorator(dec_opt_args)
def func2Bdecorated(func_opt_args):
...
#不带参数的装饰器
@dec1
@dec2
def func():
...
#这个函数声明等价于
func = dec1(dec2(func)) #带参数的装饰器
@dec(some_args)
def func():
...
#这个函数声明等价于
func = dec(some_args)(func)

不带参数的装饰器需要注意的一些细节

  这里将说明使用带参数的装饰器时需要注意的一些细节。并会实现一个简单缓存装饰器,来帮助理解。

1. 关于装饰器函数(decorator)本身

  对于被装饰的函数func,不带参数的装饰器接受一个函数为参数,并返回一个装饰过的函数decorated_func。因为对返回的函数没有限制,所以decorator函数甚至可以返回与func完全无关的新函数。但是大部分情况下,decorated_func是对func的额外处理,因此一个装饰器一般对应两个函数,一个是decorator函数,用来进行一些初始化操作处理,一个是decorated_func用来实现对被装饰的函数func的额外处理。并且为了保持对func的引用,decorated_func一般作为decorator的内部函数,比如:

#一般将decorated_func作为decorator的内部函数
#因为内部函数可以保持对func的引用(详见(1)的闭包讲解)
>>> def decorator(func):
... print 'init opration'
... def decorated_func():
... return func(2)
... return decorated_func
...

2. decorator函数只在函数声明的时候被调用一次

  装饰器实际上是语法糖,在声明函数之后就会被调用,产生decorated_func,并把func符号的引用替换为decorated_func。之后每次调用func函数,实际调用的是decorated_func。

>>> def decorator(func):
... def decorated_func():
... func(1)
... return decorated_func
...
#声明时就被调用
>>> @decorator
... def func(x):
... print x
...
decorator being called
#使用func()函数实际上使用的是decorated_func函数
>>> func()
1
>>> func.__name__
'decorated_func'

  如果要保证返回的decorated_func的函数名与func的函数名相同,应当在decorator函数返回decorated_func之前,加入decorated_func.__name__ = func.__name__, 另外functools模块提供了wraps装饰器,可以完成这一动作。

#@wraps(func)的操作相当于
#在return decorated_func之前,执行
#decorated_func.__name__ = func.__name__
#func作为装饰器参数传入,
#decorated_func则作为wraps返回的函数的参数传入
>>> def decorator(func):
... @wraps(func)
... def decorated_func():
... func(1)
... return decorated_func
...
#声明时就被调用
>>> @decorator
... def func(x):
... print x
...
decorator being called
#使用func()函数实际上使用的是decorated_func函数
>>> func()
1
>>> func.__name__
'func'

3. decorator函数局部变量的妙用

  因为closure的特性(详见(1)部分闭包部分的详解),decorator声明的变量会被decorated_func.func_closure引用,所以调用了decorator方法结束之后,decorator方法的局部变量也不会被回收,因此可以用decorator方法的局部变量作为计数器,缓存等等。值得注意的是,如果要改变变量的值,该变量一定要是可变对象,因此就算是计数器,也应当用列表来实现。并且声明一次函数调用一次decorator函数,所以不同函数的计数器之间互不冲突,例如:


#!/usr/bin/env python
#filename decorator.py
def decorator(func):
#注意这里使用可变对象
a = [0]
def decorated_func(*args,**keyargs):
func(*args, **keyargs)
#因为闭包是浅拷贝,如果是不可变对象,每次调用完成后符号都会被清空,导致错误
a[0] += 1
print "%s have bing called %d times" % (func.__name__, a[0]) return decorated_func @decorator
def func(x):
print x @decorator
def theOtherFunc(x):
print x
>>> from decorator import func
>>> from decorator import theOtherFunc
>>> func(0)
0
func have bing called 1 times
>>> func(0)
0
func have bing called 2 times
>>> func(0)
0
func have bing called 3 times
>>> theOtherFunc(0)
0
theOtherFunc have bing called 1 times
>>> theOtherFunc(1)
1
theOtherFunc have bing called 2 times
>>> theOtherFunc(2)
2
theOtherFunc have bing called 3 times

4. 简单的结果缓存装饰器

#coding=UTF-8
#!/usr/bin/env python
#filename decorator.py
import time
from functools import wraps
def decorator(func):
"cache for function result, which is immutable with fixed arguments"
print "initial cache for %s" % func.__name__
cache = {} @wraps(func)
def decorated_func(*args,**kwargs):
#key必须是可哈希对象
#这里其实不严谨,如果kwargs值有不可哈希对象会出错
#简单起见这里不再做特殊处理
key = (args, tuple(kwargs.items()))
result = None
#判断是否存在缓存
if key in cache:
(result, updateTime) = cache[key]
#过期时间固定为10秒
if time.time() -updateTime < 10:
print "cache hit for", key
else :
print "cache expired for", key
result = None
else:
print "no cache for ", key
#如果过期,或则没有缓存调用方法
if result is None:
result = func(*args, **kwargs)
cache[key] = (result, time.time())
return result return decorated_func @decorator
def func(x):
if x <=1:
return 1
return x + func(x-1)
>>> from decorator import func
initial cache for func
>>> func(5)
no cache for ((5,), ())
no cache for ((4,), ())
no cache for ((3,), ())
no cache for ((2,), ())
no cache for ((1,), ())
15
>>> func(5)
cache hit for ((5,), ())
15
>>> func(1)
cache expired for ((1,), ())
1
>>> func(2)
cache expired for ((2,), ())
cache hit for ((1,), ())
3

带参数的装饰器

  熟悉了不带参数的装饰器的使用之后,理解带参数的装饰器就简单很多了。带参数的装饰器主要用来传递一些设置,或者用来选择不同的装饰器。
  我们已经知道,不带参数的装饰器调用decorator返回decorated_func。那么带参数的装饰器,就是返回decorator方法,再由decorator方法处理后,返回decorated_func。因此带参数的装饰器一般由3个方法组成,首先,调用settings_func用来接受参数, 并选择decorator方法, 之后调用返回的decorator方法产生decorated_func来对func进行额外处理。
  有了settings_func我们就可以对decorator进行定制。如之前的实现的缓存方法,过期时间固定为10秒,有了settings_func我们就可以自定义过期时间,判断是否进行调试输出等。

1. 为缓存装饰器增加配置参数

  这里加入了对过期时间的配置,和调试输出的开关。
  

#coding=UTF-8
#!/usr/bin/env python
#filename decorator.py
import time
from functools import wraps
def cache(expirationTime, debug=False):
def decorator(func):
if debug:
print "initial cache for %s" % func.__name__
cache = {} @wraps(func)
def decorated_func(*args,**kwargs):
#key必须是可哈希对象
#这里其实不严谨,如果kwargs值有不可哈希对象会出错
#简单起见这里不再做特殊处理
key = (args, tuple(kwargs.items()))
result = None
if key in cache:
(result, updateTime) = cache[key]
if time.time() -updateTime < expirationTime:
print "cache hit for", key
else :
if debug:
print "cache expired for", key
result = None
elif debug:
print "no cache for ", key if result is None:
result = func(*args, **kwargs)
cache[key] = (result, time.time())
return result return decorated_func
return decorator @cache(10)
def func(x):
if x <=1:
return 1
return x + func(x-1)

可以看到除了cache hit,其他的消息都被debug=False关闭了。

>>> func(5)
15
>>> func(1)
cache hit for ((1,), ())
1
>>> func(2)
cache hit for ((2,), ())
3
>>> func(3)
cache hit for ((2,), ())
6

被装饰的函数共用变量

  上面的例子由于装饰器的变量(计数器,缓存)是在装饰器的方法中声明的,所以不同方法的这些变量是不通用的。要使得同一个装饰器装饰的不同方法变量通用(如共用缓存等),可以使用类属性,或全局变量来实现。

lambda表达式

  lambda表达式实际上就是匿名函数,类似javascript的function([arg1,[arg2[...]]]){...}。 python中lambda表达式返回的就是函数实例。 语法中lambda后面跟随的是参数, 冒号后面跟随的是返回的结果。

>>> bar = lambda x,y : x+y
>>> type(bar)
<type 'function'>
>>> bar(1,2)
3

同时,lambda表达式也具有closure(闭包)的特性:

>>> def foo():
... x = 5
... y = 5
... bar = lambda : x+y
... return bar
...
>>> foo()()
10

  总之,函数具有的特性,lambda表达式都具有。像给参数赋默认值啊, lambda内部的变量不受外部影响啊,全部都与函数的行为一模一样。

#默认参数
>>> y=2
>>> bar = lambda x, y =y : x + y
>>> bar(3)
5
#lambda定义的变量不受外部影响
>>> y = 5
>>> bar(3)
5
>>>

装饰器与lambda的更多相关文章

  1. Python函数小结(2)-- 装饰器、 lambda

    本篇依然是一篇学习笔记,文章的结构首先讲装饰器,然后讲lambda表达式.装饰器内容较多,先简要介绍了装饰器语法,之后详细介绍理解和使用不带参数装饰器时应当注意到的一些细节,然后实现了一个简单的缓存装 ...

  2. python day5 lambda,内置函数,文件操作,冒泡排序以及装饰器

    目录 python day 5 1. 匿名函数lambda 2. python的内置函数 3. python文件操作 4. 递归函数 5. 冒泡排序 6. 装饰器 python day 5 2019/ ...

  3. Python之路第一课Day4--随堂笔记(迭代生成装饰器)

    上节回顾: 1.集合 a.关系测试 b.去重 2.文件操作及编码 3.函数 4.局部变量和全局变量 上节回顾 本节课内容: 1.迭代器生成器 2.装饰器 3.json pickle数据序列化 4.软件 ...

  4. python高级之装饰器

    python高级之装饰器 本节内容 高阶函数 嵌套函数及闭包 装饰器 装饰器带参数 装饰器的嵌套 functools.wraps模块 递归函数被装饰 1.高阶函数 高阶函数的定义: 满足下面两个条件之 ...

  5. python基础知识7——迭代器,生成器,装饰器

    迭代器 1.迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退.另外,迭代器 ...

  6. python3.5-day5_迭代器_生成器_装饰器_模块

    笔者QQ 360212316 迭代器&生成器 生成器: 一个函数调用返回一个迭代器,那这个函数叫做生成器,如果函数中包含yield语法,那么这个函数就会变成生成器 生成器的特点: 1.生成器必 ...

  7. Python 第五天 装饰器

    装饰器 装饰器是函数,只不过该函数可以具有特殊的含义,装饰器用来装饰函数或类,使用装饰器可以在函数执行前和执行后添加相应操作. def wrapper(func): def result(): pri ...

  8. python 装饰器学习(decorator)

    最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...

  9. python 内置函数和函数装饰器

    python内置函数 1.数学相关 abs(x) 取x绝对值 divmode(x,y) 取x除以y的商和余数,常用做分页,返回商和余数组成一个元组 pow(x,y[,z]) 取x的y次方 ,等同于x ...

随机推荐

  1. sql、linq和lambda查询语句比较inner join和group by组合使用及匿名类型的处理

    使用EF自己做的小功能需要遇到inner join和group by组合使用及匿名类型的处理,搜了很多,基本不能满足自己的需要,所以总结了也实现了就自己写出来,已备查看及伙伴查询参考(一般的语句查询就 ...

  2. html的文档设置标记上(格式标记)4-5

    <html> <head> <title>第四课的标题及第五课的标题</title> <meta charset="utf-8" ...

  3. To the world you may be one person, but to one person you may be the world.

    To the world you may be one person, but to one person you may be the world.对于世界而言,你是一个人:但对于某人而言,你是他的 ...

  4. jquery获取当前被选择的复选框的value的集合

    1.HTML代码 <input type="checkbox" name="productID" value="0"> < ...

  5. 解决Layui的switch样式显示问题

    Layui官方文档是这么说的: <input type="checkbox" name="xxx" lay-skin="switch" ...

  6. C语言中字符串数组的遍历和比较

    /* The list of known types of default packet. */static char  *_default_packet_types[] = {    "d ...

  7. spring aop切面中获取代理bean的名字以及bean

    //切面中搞: Map<String , Object> map = (Map)ApplicationContextHelper.getBean(proceedingJoinPoint.g ...

  8. BCB:使用CppWebBrowser判断网页加载完成

    void __fastcall TForm1::CppWebBrowser1DocumentComplete(TObject *Sender, LPDISPATCH pDisp, Variant *U ...

  9. 《队长说得队》【Alpha】Scrum meeting 3

    项目 内容 这个作业属于哪个课程 >>2016级计算机科学与工程学院软件工程(西北师范大学) 这个作业的要求在哪里 >>实验十二 团队作业8:软件测试与ALPHA冲刺 团队名称 ...

  10. java基础—数组

    一.数组的基本概念 数组可以看成是多个相同类型数据组合,对这些数据的统一管理. 数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量. 数组的元素可以是任何数据类型,包括基 ...