本文共 10747 字,大约阅读时间需要 35 分钟。
目录
为了理解装饰器,必须首先理解在Python中函数都是对象。这一点有重要的意义,让我们通过一个简单的例子来看一看:
def shout(word="yes"): return word.capitalize() + "!"print(shout())# outputs: "Yes!"# 作为一个对象,你可以像其它对象一样,将函数赋给一个变量scream = shout# 注意这里并没有使用括号:我们没有调用函数,只是把函数名shout放进了变量scream中。这意味# 着,你可以通过scream来调用shoutprint(scream())# outputs: "Yes!"# 更近一步说,这意味着你可以删除原来的函数名shout,仍然可以通过scream来调用函数。del shouttry: 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:# 这个对象是函数的返回值之一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_decoratordef 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 wrapperdef ingredients(func): def wrapper(): print("#tomatoes#") func() print("~salad~") return wrapperdef sandwich(food="--ham--"): print(food)sandwich()# outputs: --ham--sandwich = bread(ingredients(sandwich))sandwich()# outputs:# # #tomatoes## --ham--# ~salad~# <\______/>
你设置的装饰器顺序有很大关系:
@ingredients@breaddef strange_sandwich(food="--ham--"): print(food)strange_sandwich()# outputs:# #tomatoes## # --ham--# <\______/># ~salad~
总结一下,你就很容易的看到如何解答这个问题了。
# 构造黑体的装饰器def make_bold(func): def wrapper(): return "" + func() + "" return wrapper# 构造斜体的装饰器def make_italic(func): def wrapper(): return "" + func() + "" return wrapper@make_bold@make_italicdef say(): return "hello"print(say())# outputs: hello# 它完全等同于下面这段代码:def say2(): return "hello"say2 = make_bold(make_italic(say2))print(say2())# outputs: hello
# 这不是黑魔法,有时你不得不让包装层传递参数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_argumentsdef 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 wrapperclass 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_argumentsdef 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_argumentsdef 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_argumentsdef 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_decoratordef 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@singleclass 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)中单例模式的实现原理。
转载地址:http://qbsoi.baihongyu.com/