您的位置:首页 > 编程语言 > Python开发

谈谈python的decorator,用其做cache非常棒啊

2014-01-01 16:37 399 查看

谈谈python的decorator,用其做cache非常棒啊

来自: Valley.He(叛逆的技术人) 2013-01-0517:52:48

from functools import wrapsdef cache(func):.....caches = {}.....@wraps(func).....def wrap(*args):...........if args not in caches:...................caches[args] = func(*args)...........return caches[args].....return wrap使用方法非常简单@cachedef fib(n):.....if n < 2:..........return 1.....return fib(n-1) + fib(n-2)print fib(5) #call fib so easy!下面是fib(5)调用递归图,后者是使用了cache的,可见很多重复的子调用过程取消了。从上面我们看出,我们在没有任何代码修改的前提下实现了一个简单的cache系统,记住没有修改任何代码的前提下,还有就是并不是所有的递归都适合此类方法,比如二叉树就没有必要,当且只有重复的子过程调用时采用此方法才有实际意义,反之,画蛇添足。大家还有decorator比较神奇的应用吗?原文可以查看:http://www.ibaiyang.org/2013/01/04/python-decorator-introduction/
186downvoteacceptedWhen you use a decorator, you're replacing one function with another. In other words, if you have a decorator
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
then when you say
@logged
def f(x):
"""does some math"""
return x + x * x
it's exactly the same as saying
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
and your function f is replaced with the function with_logging. Unfortunately, this means that if you then say
print f.__name__
it will print
with_logging
becausethat's the name of your new function. In fact, if you look at the docstring for f, it will be blank because with_logging has no docstring, and so the docstring you wrote won't be there anymore. Also, if you look at the pydoc result for that function, it won'tbe listed as taking one argument
x
;instead it'll be listed as taking
*args
and
**kwargs
becausethat's what with_logging takes.If using a decorator always meant losing this information about a function, it would be a serious problem. That's why we have
functools.wraps
.This takes a function used in a decorator and adds the functionality of copying over the function name, docstring, arguments list, etc. And since
wraps
isitself a decorator, the following code does the correct thing:
from functools import wrapsdef logged(func):@wraps(func)def with_logging(*args, **kwargs):print func.__name__ + " was called"return func(*args, **kwargs)return with_logging@logged
def f(x):
"""does some math"""
return x + x * xprint f.__name__  # prints 'f'print f.__doc__   # prints 'does some math'
186downvoteacceptedWhen you use a decorator, you're replacing one function with another. In other words, if you have a decorator
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
then when you say
@logged
def f(x):
"""does some math"""
return x + x * x
it's exactly the same as saying
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
and your function f is replaced with the function with_logging. Unfortunately, this means that if you then say
print f.__name__
it will print
with_logging
becausethat's the name of your new function. In fact, if you look at the docstring for f, it will be blank because with_logging has no docstring, and so the docstring you wrote won't be there anymore. Also, if you look at the pydoc result for that function, it won'tbe listed as taking one argument
x
;instead it'll be listed as taking
*args
and
**kwargs
becausethat's what with_logging takes.If using a decorator always meant losing this information about a function, it would be a serious problem. That's why we have
functools.wraps
.This takes a function used in a decorator and adds the functionality of copying over the function name, docstring, arguments list, etc. And since
wraps
isitself a decorator, the following code does the correct thing:
from functools import wrapsdef logged(func):@wraps(func)def with_logging(*args, **kwargs):print func.__name__ + " was called"return func(*args, **kwargs)return with_logging@logged
def f(x):
"""does some math"""
return x + x * xprint f.__name__  # prints 'f'print f.__doc__   # prints 'does some math'

python 装饰器和 functools 模块

什么是装饰器?

在 python 语言里第一次看到装饰器不免让人想到设计模式中的装饰模式——动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。好吧,python 中的装饰器显然和装饰模式毫无关系。那 python 中的装饰器到底是什么呢?简而言之,装饰器提供了一种方法,在函数和类定义语句的末尾插入自动运行代码。python 中有两种装饰器:函数装饰器和类装饰器。

函数装饰器

简单的装饰器例子:

1234567891011121314151617181920
def decorator(F): # 装饰器函数定义print "I'm decorator"return F@decoratordef foo():print 'Hello World!'# 上面等价于 foo = decorator(foo)foo()"""I'm decoratorHello World!"""decorator(foo)() # 所以这里的输出与 foo() 相同"""I'm decoratorHello World!"""
从上面运行后结果看出,装饰器就是一个能够返回可调用对象(函数)的可调用对象(函数)。

具有封闭作用域的装饰器

12345678910111213141516171819202122232425262728
def decorator(func): # 装饰器函数print 'in decorator'def wrapper(*args):print 'in decorator wrapper'wrapper._calls += 1print "calls = %d" % (wrapper._calls)func(*args)wrapper._calls = 0return wrapper@decoratordef foo(x, y):print "x = %d, y = %d" % (x, y)foo(1, 2) # 第一次调用"""in decoratorin decorator wrappercalls = 1x = 1, y = 2"""foo(2, 3) # 第二次调用"""in decorator wrappercalls = 2x = 2, y = 3"""
可以看出第一次调用
foo(1, 2)
时,相当于
12
foo = decorator(foo)foo(1, 2)
第二次调用
foo(2, 3)
时 foo 已经为 decorator(foo) 的返回值了再看看一个装饰器类来实现的:
1234567891011121314151617181920212223242526272829
class decorator: # 一个装饰器类def __init__(self, func):print 'in decorator __init__'self.func = funcself.calls = 0def __call__(self, *args):print 'in decorator __call__'self.calls += 1print "calls = %d" % (self.calls)self.func(*args)@decoratordef foo(x, y):print "x = %d, y = %d" % (x, y)foo(1, 2) # 第一次调用"""in decorator __init__in decorator __call__calls = 1x = 1, y = 2"""foo(2, 3) # 第二次调用"""in decorator __call__calls = 2x = 2, y = 3"""

装饰器参数

1234567891011121314151617181920212223
def decorator_wrapper(a, b):print 'in decorator_wrapper'print "a = %d, b = %d" % (a, b)def decorator(func):print 'in decorator'def wrapper(*args):print 'in wrapper'func(*args)return wrapperreturn decorator@decorator_wrapper(1, 2) # 这里先回执行 decorator_wrapper(1, 2), 返回 decorator 相当于 @decoratordef foo(word):print wordfoo('Hello World!')"""in decorator_wrappera = 1, b = 2in decoratorin wrapperHello World!"""

functools 模块

functools 模块中有三个主要的函数 partial(), update_wrapper() 和 wraps(), 下面我们分别来看一下吧。

partial(func[,args][, *keywords])

看源码时发现这个函数不是用 python 写的,而是用 C 写的,但是帮助文档中给出了用 python 实现的代码,如下:
123456789
def partial(func, *args, **keywords):def newfunc(*fargs, **fkeywords):newkeywords = keywords.copy()newkeywords.update(fkeywords)return func(*(args + fargs), **newkeywords)newfunc.func = funcnewfunc.args = argsnewfunc.keywords = keywordsreturn newfunc
OK,可能一下子没看明白,那么继续往下看,看一下是怎么用的。我们知道 python 中有个
int([x[,base]])
函数,作用是把字符串转换为一个普通的整型。如果要把所有输入的二进制数转为整型,那么就要这样写
int('11', base=2)
。这样写起来貌似不太方便,那么我们就能用 partial 来实现值传递一个参数就能转换二进制数转为整型的方法。
12345
from functools import partialint2 = partial(int, base=2)print int2('11') # 3print int2('101') # 5

update_wrapper(wrapper, wrapped[, assigned][, updated])

看这个函数的源代码发现,它就是把被封装的函数的 module, name, docdict 复制到封装的函数中去,源码如下,很简单的几句:
1234567891011
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')WRAPPER_UPDATES = ('__dict__',)def update_wrapper(wrapper,wrapped,assigned = WRAPPER_ASSIGNMENTS,updated = WRAPPER_UPDATES):for attr in assigned:setattr(wrapper, attr, getattr(wrapped, attr))for attr in updated:getattr(wrapper, attr).update(getattr(wrapped, attr, {}))return wrapper
具体如何用我们可以往下看一下。

wraps(wrapped[, assigned][, updated])

wraps() 函数把用 partial() 把 update_wrapper() 给封装了一下。
123456
def wraps(wrapped,assigned = WRAPPER_ASSIGNMENTS,updated = WRAPPER_UPDATES):return partial(update_wrapper, wrapped=wrapped,assigned=assigned, updated=updated)
好,接下来看一下是如何使用的,这才恍然大悟,一直在很多开源项目的代码中看到如下使用。
12345678910111213141516171819202122
from functools import wrapsdef my_decorator(f):@wraps(f)def wrapper(*args, **kwds):print 'Calling decorated function'return f(*args, **kwds)return wrapper@my_decoratordef example():"""这里是文档注释"""print 'Called example function'example()# 下面是输出"""Calling decorated functionCalled example function"""print example.__name__ # 'example'print example.__doc__ # '这里是文档注释'
[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: