概述 在 Python3某些强大的特性与Lisp的关系 一文中提到 Python 装饰器提供了一种定义高阶函数 的语法。从定义上说,装饰器是一个以另一个函数为参数的函数,它将隐式地扩展或修改这个函数的行为。
主要翻译自:Primer on Python Decorators by Geir Arne Hjelle 。
函数 注意 : Python 尽管不是纯粹的函数式编程语言(如与 Lisp 相比),但仍然支持许多函数式编程的概念,包括函数作为第一级状态对象(first-class objects)。(这与 python 的 everything is a object 也是保持一致的。)
###1.1 First-Class Objects 函数作为第一级对象意味着 函数可以被作为参数传递 ,就如其他对象(如 string,float, list等)一样。示例如下:
1 2 3 4 5 6 7 8 def say_hello (name ): return f"Hello {name} " def be_awesome (name ): return f"Yo {name} , together we are the awesomest!" def greet_bob (greeter_func ): return greeter_func("Bob" )
say_hello() 和 be_awesome 是普通的函数,而greet_bob() 函数将一个函数作为它的参数,例如我们可以将 say_hello 的引用传递给它:
1 2 > >> greet_bob(say_hello) 'Hello Bob'
在 Python 中,函数的参数都是引用 (具体实现可参考 Python Internals ),say_hello 作为greet_bob的参数时不会被执行,它只会在 greet_bob 的函数体中被执行。
Inner 函数 Inner函数是定义在函数中的函数,如下所示:
1 2 3 4 5 6 7 8 9 10 11 def parent (): print ("Printing from the parent() function" ) def first_child (): print ("Printing from the first_child() function" ) def second_child (): print ("Printing from the second_child() function" ) second_child() first_child()
first_child() 和 second_child() 作为 parent() 的内部函数,它们存在于parent()函数的内部域中,也就是说,如果在parent()外调用它们则会失败:
1 2 3 4 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'first_child' is not defined
函数作为返回值 函数可以作为返回值,例如:
1 2 3 4 5 6 7 8 9 10 11 12 def parent (num ): def first_child (): return "Hi, I am Emma" def second_child (): return "Call me Liam" if num == 1 : return first_child else : return second_child
parent() 函数返回的是first_child()和second_child()函数的引用,如果要调用它们,需要加’()’,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 > >> first = parent(1) > >> second = parent(2) > >> first <function parent.<locals>.first_child at 0x7f599f1e2e18> > >> second <function parent.<locals>.second_child at 0x7f599dad5268> > >> first() 'Hi, I am Emma' > >> second() 'Call me Liam'
简单的装饰器 先看一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 def my_decorator (func ): def wrapper (): print ("Something is happening before the function is called." ) func() print ("Something is happening after the function is called." ) return wrapper def say_whee (): print ("Whee!" ) say_whee = my_decorator(say_whee)
输出:
1 2 3 4 > >> say_whee() Something is happening before the function is called. Whee! Something is happening after the function is called.
say_whee() 函数的引用作为 my_decorator() 参数传入,my_decorator()函数返回其内部函数 wrapper() 的引用,并赋值给 say_whee。因此,在全局环境中,say_whee标识符不再绑定为 say_whee() 函数,而是绑定为 wrapper()函数。然而,wrapper() 函数在它的上下文环境中保存了原来的 say_whee() 函数的引用。(在这里,过程和数据的概念不再是完全对立的,过程可以是数据,数据也可以是过程。)
简单来说,装饰器包装了一个函数,修改了它的行为。
语法糖 上述装饰 say_whee() 函数的做法是有些繁琐的,所以在 Python 中允许使用 @符号来简单地使用装饰器,下面的例子做了同样的事情,但更加简洁:
1 2 3 4 5 6 7 8 9 10 11 def my_decorator (func ): def wrapper (): print ("Something is happening before the function is called." ) func() print ("Something is happening after the function is called." ) return wrapper @my_decorator def say_whee (): print ("Whee!" )
重用装饰器 装饰器函数可以单独定义在一个模块中,其他模块可以通过 import 导入它。
带有参数的装饰器 在内部包装函数中使用 ** *args ** 和 ** *kwargs **作为形参,使得它可以接受任意的位置参数和关键字参数,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def do_twice (func ): def wrapper_do_twice (*args, **kwargs ): func(*args, **kwargs) func(*args, **kwargs) return wrapper_do_twice @do_twice def say_whee (): print ("Whee!" ) @do_twice def greet (name ): print (f"Hello {name} " )
测试:
1 2 3 4 5 6 7 8 > >> say_whee() Whee! Whee! > >> greet("World" ) Hello World Hello World
从装饰器函数中返回值 如果被装饰的函数有返回值,我们只需要在包装函数中返回这个返回值即可。如下所示:
1 2 3 4 5 6 7 8 9 10 def do_twice (func ): def wrapper_do_twice (*args, **kwargs ): func(*args, **kwargs) return func(*args, **kwargs) return wrapper_do_twice @do_twice def return_greeting (name ): print ("Creating greeting" ) return f"Hi {name} "
1 2 3 4 > >> return_greeting("Adam" ) Creating greeting Creating greeting 'Hi Adam'
更有用的例子