python的函数是对象
要理解装饰器,首先,你必须明白,在python中,函数是对象. 这很重要.
简单例子来理解为什么
好了,记住这点,我们将会很快用到它.
Python函数另一个有趣的特性是,函数可以被定义在另一个函数里面
函数引用
好了,到这里了,接下来是有意思的部分,我们刚才看到 函数是对象,然后:
1.函数可以赋值给一个变量
2.函数可以定义在另一个函数内部
即,这也意味着一个函数可以返回另一个函数:-),让我们来看另一段代码
好了,现在你已经了解要理解装饰器的每件事.
装饰器就是封装器,可以让你在被装饰函数之前或之后执行代码,而不必修改函数本身
手工装饰器
如何书写一个装饰器
装饰器是一个以另一个函数为参数的函数
|
|
是的,就是这么简单. @decorator是下面代码的简写
装饰器只是 装饰器模式的python实现
python代码中还存在其他几个经典的设计模式,以方便开发,例如迭代器iterators
当然,你可以累加装饰器
装饰方法
Python中对象的方法和函数是一样的,除了对象的方法首个参数是指向当前对象的引用(self)。这意味着你可以用同样的方法构建一个装饰器,只是必须考虑self
当然,你可以构造一个更加通用的装饰器,可以作用在任何函数或对象方法上,而不必关系其参数
使用
*args, **kwargs
如下代码
向装饰器传递参数
好了,现在你或许会想是否可以向装饰器本身传递参数
装饰器必须使用函数作为参数,所以这看起来会有些复杂,你不能直接传递参数给装饰器本身
在开始处理这个问题前,看一点提醒
装饰器是普通的方法
|
|
到这里,我们使用@调用一个函数
回到问题,向装饰器本身传递参数,如果我们可以通过函数去创建装饰器,那么我们可以传递参数给这个函数,对么?
但是记住,装饰器仅在Python代码导入时被调用一次,之后你不能动态地改变参数.当你使用”import x”,函数已经被装饰,所以你不能改变什么
练习:一个装饰装饰器的装饰器
作为奖励,我将展示创建可以处理任何参数的装饰器代码片段. 毕竟,为了接收参数,必须使用另一个函数来创建装饰器
让我们来给装饰器写一个装饰器:
```
装饰 装饰器 的装饰器 (好绕…..)
def decorator_with_args(decorator_to_enhance):
“””
这个函数将作为装饰器使用
它必须装饰另一个函数
它将允许任何接收任意数量参数的装饰器
方便你每次查询如何实现
“””
# 同样的技巧传递参数
def decorator_maker(*args, **kwargs):
# 创建一个只接收函数的装饰器
# 但是这里保存了从创建者传递过来的的参数
def decorator_wrapper(func):
# 我们返回原始装饰器的结果
# 这是一个普通的函数,返回值是另一个函数
# 陷阱:装饰器必须有这个特殊的签名,否则不会生效
return decorator_to_enhance(func, *args, **kwargs)
return decorator_wrapper
return decorator_maker
使用:
你创建这个函数是作为一个装饰器,但是给它附加了一个装饰器
别忘了,函数签名是: “decorator(func, args, *kwargs)”
@decorator_with_args
def decorated_decorator(func, args, *kwargs):
def wrapper(function_arg1, function_arg2):
print “Decorated with”, args, kwargs
return func(function_arg1, function_arg2)
return wrapper
然后,使用这个装饰器(your brand new decorated decorator)
@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
print “Hello”, function_arg1, function_arg2
decorated_function(“Universe and”, “everything”)
#outputs:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything
Whoooot!
我知道,到现在你一定会有这种感觉,就像你听一个人说“在理解递归之前,你必须首先了解递归”,但是现在,掌握这儿你有没有觉得很棒?
装饰器使用最佳实践
这是Python2.4的新特性,所以确保你的代码在2.4及之上的版本运行
装饰器降低了函数调用的性能,记住这点
You can not un-decorate a function. There are hacks to create decorators that can be removed but nobody uses them. So once a function is decorated, it’s done. For all the code.
装饰器包装函数,所以很难debug
Python2.5解决了最后一个问题,它提供functools模块,包含functools.wraps.这个函数会将被装饰函数的名称,模块,文档字符串拷贝给封装函数,有趣的是,functools.wraps是一个装饰器:-)
调试,打印函数的名字
def foo():
print “foo”
print foo.name
#outputs: foo
但当你使用装饰器,这一切变得混乱
def bar(func):
def wrapper():
print “bar”
return func()
return wrapper
@bar
def foo():
print “foo”
print foo.name
#outputs: wrapper
“functools” 可以改变这点
import functools
def bar(func):
# 我们所说的 "wrapper", 封装 "func"
@functools.wraps(func)
def wrapper():
print "bar"
return func()
return wrapper
@bar
def foo():
print “foo”
得到的是原始的名称, 而不是封装器的名称
print foo.name
#outputs: foo
装饰器为何那么有用
现在的问题是,我们用装饰器来坐什么?看起来很酷很强大,但是如果有实践的例子会更好.好了,有1000种可能。经典的用法是,在函数的外部,扩展一个函数的行为(你不需要改变这个函数),或者,为了调试的目的(我们不修改的原因是这是临时的),你可以使用装饰器扩展一些函数,而不用在这些函数中书写相同的函数实现一样的功能
DRY原则,例子:
def benchmark(func):
“””
装饰器打印一个函数的执行时间
“””
import time
def wrapper(args, **kwargs):
t = time.clock()
res = func(args, **kwargs)
print func.name, time.clock()-t
return res
return wrapper
def logging(func):
“””
装饰器记录函数日志
“””
def wrapper(args, **kwargs):
res = func(args, **kwargs)
print func.name, args, kwargs
return res
return wrapper
def counter(func):
“””
记录并打印一个函数的执行次数
“””
def wrapper(args, **kwargs):
wrapper.count = wrapper.count + 1
res = func(args, **kwargs)
print “{0} has been used: {1}x”.format(func.name, wrapper.count)
return res
wrapper.count = 0
return wrapper
@counter
@benchmark
@logging
def reverse_string(string):
return str(reversed(string))
print reverse_string(“Able was I ere I saw Elba”)
print reverse_string(“A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!”)
#outputs:
#reverse_string (‘Able was I ere I saw Elba’,) {}
#wrapper 0.0
#wrapper has been used: 1x
#ablE was I ere I saw elbA
#reverse_string (‘A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!’,) {}
#wrapper 0.0
#wrapper has been used: 2x
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A
装饰器意味着,你可以用正确的方法实现几乎所有的事情,而不必重写他们
@counter
@benchmark
@logging
def get_random_futurama_quote():
import httplib
conn = httplib.HTTPConnection(“slashdot.org:80”)
conn.request(“HEAD”, “/index.html”)
for key, value in conn.getresponse().getheaders():
if key.startswith(“x-b”) or key.startswith(“x-f”):
return value
return “No, I’m … doesn’t!”
print get_random_futurama_quote()
print get_random_futurama_quote()
#outputs:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper has been used: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper has been used: 2x
#Curse you, merciful Poseidon!
Python本身提供了一些装饰器:property,staticmethod,等等,
Django使用装饰器去管理缓存和权限. Twisted to fake inlining asynchronous functions calls.用途广泛
EDIT: 鉴于这个回答的完美,人们希望我去回答metaclass,我这样做了