目录
函数即对象
为了理解装饰器,必须首先理解在Python中函数都是对象。这一点有重要的意义,让我们通过一个简单的例子来看一看:
def shout(word="yes"):
return word.capitalize() + "!"
print(shout())
# outputs: "Yes!"
# 作为一个对象,你可以像其它对象一样,将函数赋给一个变量
scream = shout
# 注意这里并没有使用括号:我们没有调用函数,只是把函数名shout放进了变量scream中。这意味
# 着,你可以通过scream来调用shout
print(scream())
# outputs: "Yes!"
# 更近一步说,这意味着你可以删除原来的函数名shout,仍然可以通过scream来调用函数。
del shout
try:
print(shout())
except NameError as e:
print(str(e))
# outputs: "name 'shout' is not defined!"
print(scream())
# outputs: "Yes!"
好的!把这个记录下来,稍后我们再回头看这里。Python函数另外一个有趣的特性是:它可以被定义在另外一个函数中。定义一个talk函数,在talk函数内部被定义一个whisper函数:
def talk():
# 你可以在函数talk运行时定义其它函数
def whisper(word="yes") -> str:
return word.lower() + "..."
# 并立即使用它!
print(whisper())
# 你可以调用talk,每次调用的时候都会定义whisper,然后whisper在talk中被调用
talk()
# outputs: "yes..."
# 但是whisper在talk之外并不存在
try:
print(whisper())
except NameError as e:
print(str(e))
# outputs: "name 'whisper' is not defined"
函数参考
接下来是有趣的部分,你已经看到了,函数就是对象,因此:
-
它可以被赋给另一个变量
-
它可以在另一个函数中被定义
这意味着,函数可以返回另外一个函数。来看看吧:
def get_talk(type="shout"):
def shout(word="yes"):
return word.capitalize() + "!"
def whisper(word="yes"):
return word.lower() + "..."
# 然后我们返回其中一个
if type == "shout":
# 这里没有使用括号,并不是在调用函数。这里返回的是函数对象
return shout
else:
return whisper
# 那么怎么使用呢?
# 获取返回的函数并把它赋给一个变量
talk = get_talk()
# 你可以看到talk在这里是一个函数对象
print(talk)
# outputs: <function shout at 0xb7ea817c>
# 这个对象是函数的返回值之一
print(talk())
# outputs: "Yes!"
# 更疯狂一点,甚至你可以直接使用它
print(get_talk("whisper")())
# outputs: "yes..."
不过,等一等,这里还有更多。如果你能返回一个函数,那么你也能用它做参数来传递一个。
def do_something_before(func):
print("I do something before then I call the function you gave me")
print(func())
def scream() -> str:
return "Yes!"
do_something_before(scream)
# output: "
# I do something before then I call the function you gave me
# Yes! "
现在你已经掌握了你要理解装饰器所需要的一切知识了。装饰器就是一个包装,它的意思是,“他们让你在函数运行前后执行被装饰过的代码”,而不需要去修改函数本身。
手工制作装饰器
你怎么会想要手动做装饰器:
# 装饰器是一个函数,它期望由另外一个函数作为其参数
def my_shiny_new_decorator(a_function_to_decorate):
# 在内部,装饰器在运行时定义了一个函数:一个包装。
# 这个函数会将原始函数包装起来,所以在其前后执行代码。
def the_wrapper_around_the_originak_function():
# 在这里添加原始函数调用之前你所希望执行的代码
print("Before the function runs")
# 调用函数(记得使用括号)
a_function_to_decorate()
# 在这里添加在原始函数调用之后你所希望执行的代码
print("After the function runs")
# 在这个位置,a_function_to_decorate函数已经被执行了。我们返回刚才创建的包装函数。这个包装
# 一个函数及其前后要执行的代码。它已经能够使用了!
return the_wrapper_around_the_originak_function
# 现在想象你创建了一个函数,但是你再也不想碰它了。
def a_stand_alone_function():
print("I am a stand alone function, do not you dare modify me")
a_stand_alone_function()
# outputs: I am a stand alone function, do not you dare modify me
# 你也可以通过装饰器来扩展它们的行为
# 仅仅把它传递给装饰器,就可以用任何你所想要的代码进行动态的包装,并返回一个你可以使用的新函数
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, do not you dare modify me
# After the function runs
现在,你可能想每次调用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, do not you dare modify me
# After the function runs
猜猜怎么样?这恰恰就是装饰器所做的事情!
装饰器解密
先前的例子,就使用了装饰器语法:
@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
这就是全部了,如此简单。@my_shiny_new_decorator就是下面这种形式的简写:
another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
装饰器就仅仅是装饰器设计模式在Python中的变异。有很多不同的经典设计模式都嵌入到Python中来方便开发,比如迭代器。
当然,你也可以对装饰器进行累加:
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~
# <\______/>
你设置的装饰器顺序有很大关系:
@ingredients
@bread
def strange_sandwich(food="--ham--"):
print(food)
strange_sandwich()
# outputs:
# #tomatoes#
# </......\>
# --ham--
# <\______/>
# ~salad~
实际解答开头的问题
总结一下,你就很容易的看到如何解答这个问题了。
# 构造黑体的装饰器
def make_bold(func):
def wrapper():
return "<b>" + func() + "</b>"
return wrapper
# 构造斜体的装饰器
def make_italic(func):
def wrapper():
return "<i>" + func() + "</i>"
return wrapper
@make_bold
@make_italic
def say():
return "hello"
print(say())
# outputs: <b><i>hello</i></b>
# 它完全等同于下面这段代码:
def say2():
return "hello"
say2 = make_bold(make_italic(say2))
print(say2())
# outputs: <b><i>hello</i></b>
向被装饰函数传递参数
# 这不是黑魔法,有时你不得不让包装层传递参数
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
# 既然当你调用装饰器返回的函数时,你调用了包装层,那么给包装层传递参数就能够把它们
# 传递给装饰器函数
@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
包装层的参数必须与被装饰函数的参数完全一致。
装饰方法
Python最伟大的地方就在于方法和函数完全相同,除了方法期望它的第1个参数是当前对象的第一个引用(self)(这里说明一下,类方法第一个期望的参数是cls,静态方法则没有第一个固定的期望参数)。这就意味着,你能够用同样的方式为方法建立装饰器,记得要把self/cls考虑上:
def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
# 非常友好,减少更多的年龄
lie -= 3
return method_to_decorate(self, lie)
return wrapper
class Lucy(object):
def __init__(self):
self.age = 32
@method_friendly_decorator
def say_your_age(self, lie):
print("I am %s, what did you think?" % str(self.age + lie))
i = Lucy()
i.say_your_age(-3)
# outputs:
# I am 26, what did you think?
当然,如果你制作了一个非常通用的装饰器,并且希望应用到任何函数和方法,不用考虑它的参数,使用位置参数*args和关键字参数**kwargs就可以了。
def a_decorator_passing_arbitrary_arguments(function):
def wrapper(*args, **kwargs):
print("Do I have args?")
print(args)
print(kwargs)
# 然后解包参数,这里是*args, **kwargs
function(*args, **kwargs)
return wrapper
@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 say_your_age(self, lie=-3):
print("I am %s, what did you think?" % (self.age + lie))
m = Mary()
m.say_your_age()
# outputs:
# Do I have args?
# (<__main__.Mary object at 0x104e3bbb0>,)
# {}
# I am 28, what did you think?
给装饰器传递参数
现在,给装饰器本身传递参数会怎么样呢?这个有点绕,因为装饰器本身就要接收一个函数来作为参数,因此,你不能直接传递被装饰函数的参数来给装饰器。
在解决这个问题之前,先来看一些提醒:
# 装饰器就是普通函数
def my_decorator(func):
print("I am a ordinary function.")
def wrapper():
print("I am function returned by the decorator")
func()
return wrapper
# 因此,你可以不用'@'符号就可以调用它
def lazy_function():
print("zzz")
decorated_function = my_decorator(lazy_function)
# outputs: I am a ordinary function.
# 它输出 "I am a ordinary function.",因为你所做的仅仅就是"调用一个函数",
# 没什么神奇的。
@my_decorator
def lazy_function2():
print("zzz")
# outputs: I am a ordinary function.
它完全相同。"my_decorator"被调用。所以当你@my_decorator的时候,其实是你告诉Python要调用一个标志为"my_decorator"变量名的函数。这很重要,因为你给出的标签可以直接指向装饰器……或者没有!让我们开始邪恶一些吧!
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 wrapper():
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 wrapper function.")
return wrapper
print("As a decorator maker, I return a decorator.")
return my_decorator
# 让我们创建一个装饰器,实际上它就是一个新函数
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.
# 然后我们来装饰函数
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 wrapper 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.
就像你看到的,你可以使用这个技巧,像使用任何函数一样,给装饰器传递参数。如果你喜欢,你甚至可以使用*args, **kwargs。但是记住,装饰器仅仅只在被Python导入脚本的时候调用一次。之后,你就不能动态地设置参数了。当你"import x"的时候,函数就已经被装饰了,所以你什么都不能修改了。
下面的代码展示了执行3次demo()函数:
def new_decorator(times):
def test(func):
def wrapper(*args, **kwargs):
for i in range(times):
func(*args, **kwargs)
print("------")
return wrapper
return test
@new_decorator(3)
def demo():
print("666")
demo()
# outputs:
# 666
# 666
# 666
# ------
装饰类
至此,我们已经熟练的掌握了如何使用装饰器去装饰一个函数或一个方法。在Python的世界里,一切皆为对象。因此装饰器同样可以去装饰一个class。与装饰函数和方法的原理相同,对于装饰器来讲,class仅仅也是一个对象。
def single(cls):
instance = {}
def wrapper(*args, **kwargs):
if cls not in instance:
instance[cls] = cls(*args, **kwargs)
return instance[cls]
return wrapper
@single
class Twins(object):
def __init__(self, name):
self.name = name
def introduction(self):
print("Hi, My name is %s." % self.name)
bob = Twins("Bob")
tom = Twins("Tom")
bob.introduction()
# outputs: Hi, My name is Bob.
tom.introduction()
# outputs: Hi, My name is Bob.
是的,你没有看错。Twins类实例出来的对象都显示了一样的内容。这是因为single装饰器的作用,Twins类仅仅只被真正实例化了一次。这个例子也正是设计模式(Design Pattern)中单例模式的实现原理。
装饰器注意事项
- 它们是Python2.4版本新加入的,请确保你使用的版本能够运行你的代码
- 装饰器会降低被装饰函数调用的速度,请记住这一点
- 你不能反装饰一个函数。有一些创建可删除装饰器的技巧但是没有人使用它们。所以一旦函数被装饰过,它就固定下来了。对于所有代码都是如此。
- 装饰器包装函数,会使得代码很难调试
- 装饰器的优点在于,不用重写函数,就可以立刻拓展函数的功能