在stackoverflow上面看到一个关于Python中装饰器问题的回复,瞬间觉得作者简直是神人啊。
原文地址:http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
这么好的一段东西,实在是忍不住拿过来翻译一下,有删改:
Python's functions are objects
要想理解装饰器,首先需要明白在Python世界里,函数也是一种对象。让我们看下下面这一个例子:
def shout(word="yes"):
return word.capitalize()+"!"
print shout()
# outputs : 'Yes!'
# 输出 : 'Yes!'
# As an object, you can assign the function to a variable like any
# other object
# 如果函数是个对象,那么它就可以赋值给变量
scream = shout
# Notice we don't use parentheses: we are not calling the function, we are
# putting the function "shout" into the variable "scream".
# It means you can then call "shout" from "scream":
# 注意这里并没有使用括号,也就是说没有调用这个函数
# 我们只是让变量 "scream" 等于函数 "shout"
# 意思就是你通过调用 "scream" 函数来调用 "shout" 函数:
print scream()
# outputs : 'Yes!'
# More than that, it means you can remove the old name 'shout', and
# the function will still be accessible from 'scream'
# 另外,这还表示你可以删除老的函数名 'shout'
# 这个函数依然存在,并且可以通过 'scream' 调用
del shout
try:
print shout()
except NameError, e:
print e
#outputs: "name 'shout' is not defined"
print scream()
# outputs: 'Yes!'
OK, keep that in mind, we are going back to it soon. Another interesting property of Python functions is they can be defined... inside another function!
好了,函数也是一个对象的问题,暂时先讨论到这里。函数还有另外一个很有意思的属性:我们可以在函数里面定义一个函数!
def talk():
# You can define a function on the fly in "talk" ...
# 你可以在 "talk" 函数运行的时候定义一个函数 ...
def whisper(word="yes"):
return word.lower()+"..."
# ... and use it right away!
# ... 紧接着调用这个函数!
print whisper()
# You call "talk", that defines "whisper" EVERY TIME you call it, then
# "whisper" is called in "talk".
# 每调用一次 "talk" 函数,就会定义一个 "whisper" 函数,
# 然后再调用这个刚刚定义的 "whisper" 函数 .
talk()
# outputs:
# "yes..."
# But "whisper" DOES NOT EXIST outside "talk":
# 并且 "whisper" 函数在 "talk" 函数外部是不可见的:
try:
print whisper()
except NameError, e:
print e
#outputs : "name 'whisper' is not defined"*
Functions references
def getTalk(type="shout"):
# We define functions on the fly
# 定义一个函数
def shout(word="yes"):
return word.capitalize()+"!"
def whisper(word="yes") :
return word.lower()+"...";
# Then we return one of them
# 返回其中的而一个函数
if type == "shout":
# We don't use "()", we are not calling the function,
# we are returning the function object
# 再次注意:这里没有使用"()",我们并没有调用函数,而是将它作为返回值返回出去
return shout
else:
return whisper
# How do you use this strange beast?
# 刚刚这函数写得那么纠结,到底有什么用呢?
# Get the function and assign it to a variable
# 调用 getTalk 函数,将返回的函数赋值给一个变量
talk = getTalk()
# You can see that "talk" is here a function object:
# 现在 "talk" 变成了一个函数对象
print talk
#outputs :
# The object is the one returned by the function:
# 看下调用这个函数会返回什么
print talk()
#outputs : Yes!
# And you can even use it directly if you feel wild:
# 当然您也可以直接调用它:
print getTalk("whisper")()
#outputs : yes...
But wait, there is more. If you can return a function, then you can pass one as a parameter:
还有更屌的,你既然可以返回一个函数,当然也可以接受一个函数作为参数
def doSomethingBefore(func):
print "I do something before then I call the function you gave me"
print func()
doSomethingBefore(scream)
#outputs:
#I do something before then I call the function you gave me
#Yes!
Well, you just have everything needed to understand decorators. You see, decorators are wrappers which means that they let you execute code before and after the function they decorate without the need to modify the function itself.
好了,你已经搞明白了理解装饰器所需的所有基础知识。装饰器(decorator) 就是一个包装机(wrapper),让你能够在不修改原始函数的基础上,在执行函数的前后加入额外的代码
手动实现一个装饰器:
# A decorator is a function that expects ANOTHER function as parameter
# 装饰器是一个函数,这个函数接收一个函数作为参数
def my_shiny_new_decorator(a_function_to_decorate):
# Inside, the decorator defines a function on the fly: the wrapper.
# This function is going to be wrapped around the original function
# so it can execute code before and after it.
# 在装饰器(decorator)内部定义了一个函数即前面提到的包装机(wrapper)
# 这个函数在原始函数的上下添加了一些代码
# 这些代码在原始函数调用的前后执行.
def the_wrapper_around_the_original_function():
# Put here the code you want to be executed BEFORE the original
# function is called、
# 在原始函数前面添加代码,以便在原始函数调用之前执行
print "Before the function runs"
# Call the function here (using parentheses)
# 通过装饰器函数的参数调用原始函数
a_function_to_decorate()
# Put here the code you want to be executed AFTER the original
# function is called
# 在原始函数的后面添加代码,以便在原始函数调用之后执行
print "After the function runs"
# At this point, "a_function_to_decorate" HAS NEVER BEEN EXECUTED.
# We return the wrapper function we have just created.
# The wrapper contains the function and the code to execute before
# and after. It's ready to use!
# 函数执行到这里,"a_function_to_decorate" 到目前为止还没有执行
# 我们返回刚刚创建的包装函数(wrapper function)
# 这个包装函数(wrapper function)包含那些在原始函数执行前后需要被执行的代码
# 这个返回的包装函数(wrapper function)可以被正常调用
return the_wrapper_around_the_original_function
# Now imagine you create a function you don't want to ever touch again.
# 加入你写了一个函数,你再也不想去碰它了
def a_stand_alone_function():
print "I am a stand alone function, don't you dare modify me"
a_stand_alone_function()
#outputs: I am a stand alone function, don't you dare modify me
# Well, you can decorate it to extend its behavior.
# Just pass it to the decorator, it will wrap it dynamically in
# any code you want and return you a new function ready to be used:
# 为了给这个函数添加一些功能,你可以修饰(decorate)它。
# 只要将这个函数作为参数传递给一个装饰器函数,
# 那么这个装饰器函数就会动态的为这个待修饰的原始函数前后添加一些代码
# 并且返回一个新函数给你
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
Now, you probably want that every time you call a_stand_alone_function, a_stand_alone_function_decorated is called instead. That's easy, just overwrite a_stand_alone_function with the function returned by my_shiny_new_decorator:
你可能觉得每次调用a_stand_alone_function函数的时候,都要用a_stand_alone_function_decorated来代替,这样一点都不优雅。解决这个问题其实很简单,只要把my_shiny_new_decorator函数返回的函数重新赋值给a_stand_alone_function就可以了。
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#outputs:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
# And guess what? That's EXACTLY what decorators do!
# 你猜怎么着?Python里面的装饰器就是这么搞的
Decorators demystified
之前的那个例子,用decorator语法表示如下:
@my_shiny_new_decorator
def another_stand_alone_function():
print "Leave me alone"
another_stand_alone_function()
#outputs:
#Before the function runs
#Leave me alone
#After the function runs
Yes, that's all, it's that simple. @decorator is just a shortcut to:
没错!就是那么简单!!@decorator 其实就是下面这段代码的简单写法。
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
Decorators are just a pythonic variant of the decorator design pattern. There are several classic design patterns embedded in Python to ease development, like iterators.
装饰器其实就是装饰器模式的一种Python形式的方言。在Python里面还内置了一些其他的可以加速开发效率的典型的设计模式,比如说迭代器(iterators)当然,你也可以同时使用多个装饰器:
def bread(func):
def wrapper():
print "''''''\>"
func()
print "<\______/>"
return wrapper
def ingredients(func):
def wrapper():
print "#tomatoes#"
func()
print "~salad~"
return wrapper
def sandwich(food="--ham--"):
print food
sandwich()
#outputs: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
Using the Python decorator syntax:
使用Python的装饰器语法
@bread
@ingredients
def sandwich(food="--ham--"):
print food
sandwich()
#outputs:
#''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
The order you set the decorators MATTERS:
装饰器的使用顺序也是有影响的
@ingredients
@bread
def strange_sandwich(food="--ham--"):
print food
strange_sandwich()
#outputs:
##tomatoes#
#''''''\>
# --ham--
#<\______/>
# ~salad~
# It's not black magic, you just have to let the wrapper
# pass the argument:
# 这一点都不神奇,只要让包装器(wrapper)传递参数就可以了
def a_decorator_passing_arguments(function_to_decorate):
def a_wrapper_accepting_arguments(arg1, arg2):
print "I got args! Look:", arg1, arg2
function_to_decorate(arg1, arg2)
return a_wrapper_accepting_arguments
# Since when you are calling the function returned by the decorator, you are
# calling the wrapper, passing arguments to the wrapper will let it pass them to
# the decorated function
# 当你调用通过装饰器包装过后返回的函数时,
# 相当于调用包装器,并且将参数传递给包装器,由包装器将参数传递给原始函数。
@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
print "My name is", first_name, last_name
print_full_name("Peter", "Venkman")
# outputs:
#I got args! Look: Peter Venkman
#My name is Peter Venkman
def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
lie = lie - 3 # very friendly, decrease age even more :-)
return method_to_decorate(self, lie)
return wrapper
class Lucy(object):
def __init__(self):
self.age = 32
@method_friendly_decorator
def sayYourAge(self, lie):
print "I am %s, what did you think?" % (self.age + lie)
l = Lucy()
l.sayYourAge(-3)
#outputs: I am 26, what did you think?
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
# The wrapper accepts any arguments
def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
print "Do I have args?:"
print args
print kwargs
# Then you unpack the arguments, here *args, **kwargs
# If you are not familiar with unpacking, check:
# http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
function_to_decorate(*args, **kwargs)
return a_wrapper_accepting_arbitrary_arguments
@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
print "Python is cool, no argument here."
function_with_no_argument()
#outputs
#Do I have args?:
#()
#{}
#Python is cool, no argument here.
@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
print a, b, c
function_with_arguments(1,2,3)
#outputs
#Do I have args?:
#(1, 2, 3)
#{}
#1 2 3
@a_decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, platypus="Why not ?"):
print "Do %s, %s and %s like platypus? %s" %\
(a, b, c, platypus)
function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")
#outputs
#Do I have args ? :
#('Bill', 'Linus', 'Steve')
#{'platypus': 'Indeed!'}
#Do Bill, Linus and Steve like platypus? Indeed!
class Mary(object):
def __init__(self):
self.age = 31
@a_decorator_passing_arbitrary_arguments
def sayYourAge(self, lie=-3): # You can now add a default value
print "I am %s, what did you think ?" % (self.age + lie)
m = Mary()
m.sayYourAge()
#outputs
# Do I have args?:
#(<__main__.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?
# Decorators are ORDINARY functions
# 装饰器是一个普通的函数
def my_decorator(func):
print "I am a ordinary function"
def wrapper():
print "I am function returned by the decorator"
func()
return wrapper
# Therefore, you can call it without any "@"
# 你可以直接调用它而不使用 "@"
def lazy_function():
print "zzzzzzzz"
decorated_function = my_decorator(lazy_function)
#outputs: I am a ordinary function
# It outputs "I am a ordinary function", because that's just what you do:
# calling a function. Nothing magic.
# 输出 "I am a ordinary function",没什么特别的
@my_decorator
def lazy_function():
print "zzzzzzzz"
#outputs: I am a ordinary function
def decorator_maker():
print "I make decorators! I am executed only once: "+\
"when you make me create a decorator."
def my_decorator(func):
print "I am a decorator! I am executed only when you decorate a function."
def wrapped():
print ("I am the wrapper around the decorated function. "
"I am called when you call the decorated function. "
"As the wrapper, I return the RESULT of the decorated function.")
return func()
print "As the decorator, I return the wrapped function."
return wrapped
print "As a decorator maker, I return a decorator"
return my_decorator
# Let's create a decorator. It's just a new function after all.
# 现在创建一个装饰器,实际是一个新的函数。
new_decorator = decorator_maker()
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
# Then we decorate the function
# 然后我们用它装饰一个函数
def decorated_function():
print "I am the decorated function."
decorated_function = new_decorator(decorated_function)
#outputs:
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function
# Let's call the function:
# 最后调用这个函数
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.
def decorated_function():
print "I am the decorated function."
decorated_function = decorator_maker()(decorated_function)
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.
# Finally:
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.
@decorator_maker()
def decorated_function():
print "I am the decorated function."
#outputs:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.
#Eventually:
decorated_function()
#outputs:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
print "I make decorators! And I accept arguments:", decorator_arg1, decorator_arg2
def my_decorator(func):
# The ability to pass arguments here is a gift from closures.
# If you are not comfortable with closures, you can assume it's ok,
# or read: http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python
print "I am the decorator. Somehow you passed me arguments:", decorator_arg1, decorator_arg2
# Don't confuse decorator arguments and function arguments!
def wrapped(function_arg1, function_arg2) :
print ("I am the wrapper around the decorated function.\n"
"I can access all the variables\n"
"\t- from the decorator: {0} {1}\n"
"\t- from the function call: {2} {3}\n"
"Then I can pass them to the decorated function"
.format(decorator_arg1, decorator_arg2,
function_arg1, function_arg2))
return func(function_arg1, function_arg2)
return wrapped
return my_decorator
@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
print ("I am the decorated function and only knows about my arguments: {0}"
" {1}".format(function_arg1, function_arg2))
decorated_function_with_arguments("Rajesh", "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Sheldon
# - from the function call: Rajesh Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard
c1 = "Penny"
c2 = "Leslie"
@decorator_maker_with_arguments("Leonard", c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
print ("I am the decorated function and only knows about my arguments:"
" {0} {1}".format(function_arg1, function_arg2))
decorated_function_with_arguments(c2, "Howard")
#outputs:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Penny
# - from the function call: Leslie Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Leslie Howard
def decorator_with_args(decorator_to_enhance):
"""
This function is supposed to be used as a decorator.
It must decorate an other function, that is intended to be used as a decorator.
Take a cup of coffee.
It will allow any decorator to accept an arbitrary number of arguments,
saving you the headache to remember how to do that every time.
"""
# We use the same trick we did to pass arguments
def decorator_maker(*args, **kwargs):
# We create on the fly a decorator that accepts only a function
# but keeps the passed arguments from the maker.
def decorator_wrapper(func):
# We return the result of the original decorator, which, after all,
# IS JUST AN ORDINARY FUNCTION (which returns a function).
# Only pitfall: the decorator must have this specific signature or it won't work:
return decorator_to_enhance(func, *args, **kwargs)
return decorator_wrapper
return decorator_maker
# You create the function you will use as a decorator. And stick a decorator on it :-)
# Don't forget, the signature is "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
# Then you decorate the functions you wish with 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!
# For debugging, the stacktrace prints you the function __name__
def foo():
print "foo"
print foo.__name__
#outputs: foo
# With a decorator, it gets messy
def bar(func):
def wrapper():
print "bar"
return func()
return wrapper
@bar
def foo():
print "foo"
print foo.__name__
#outputs: wrapper
# "functools" can help for that
import functools
def bar(func):
# We say that "wrapper", is wrapping "func"
# and the magic begins
@functools.wraps(func)
def wrapper():
print "bar"
return func()
return wrapper
@bar
def foo():
print "foo"
print foo.__name__
#outputs: foo
def benchmark(func):
"""
A decorator that prints the time a function takes
to execute.
一个输出函数运行时间的装饰器
"""
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):
"""
A decorator that logs the activity of the script.
一个输出日志信息的装饰器
(it actually just prints it, but it could be logging!)
虽然这里只是简单得只用了print函数,但是你可以用其他日志模块代替
"""
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print func.__name__, args, kwargs
return res
return wrapper
def counter(func):
"""
A decorator that counts and prints the number of times a function has been executed
一个记录、打印函数调用次数的装饰器
"""
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!