[译]python 修饰器 (Decorators)
简介
修饰器应该是python语言里面最漂亮最强大的设计.同时也被认为是难学的.准确来说,使用修饰器是简单,但是设计修饰器是复杂的,尤其是你没有足够的修饰器和函数式编程的相关经验时.
虽然底层设计理念相同,但是我们还是有两种不同的修饰器:
- 函数修饰器(Function decorator)
- 类修饰器
python里修饰器是一个可以调用的对象,用来改变一个函数或者类.
一个函数的引用”func”或者类的引用(c),以参数的形式传递给修饰器,修饰器返回返回一个修改后的函数或对象.通常这个修改后的函数或对象包含原来函数”func”或原来类”c”.
你也可以参看memoization with decorators.(尚未翻译)
(这原教程上一张配图)
如果你对这张图感兴趣,想学习图像处理相关知识,像 Numpy Scipy和Matplotlib这些技术,你一定要参看Image Processing Technoques,文章详细介绍使用修饰器的一些细节
迈向修饰器的第一步
在我们的教程中,修饰器的定义可能会让许多初学这困惑.
因此,我先从重复修饰器几个重要的方面来引入修饰器.首先得记住函数的名称是对函数的引用,同时我们可以让多个名字(name)指向同一个函数:
>>> def succ(x):
... return x + 1
...
>>> successor = succ
>>> successor(10)
11
>>> succ(10)
11
上面例子,我们有两个不同的名字”succ”和”successor” 指向同一个函数.
另一个重要的的点是,我们可以删除 “succ”或者”successor”,而不是失去对函数本身的引用:
>>> del succ
>>> successor(10)
11
函数内的函数(Functions inside Functions)
在一个函数里面定义另一个函数对c和c++ 程序员来书是一个完全陌生的概念:
def f():
def g():
print("Hi, it's me 'g'")
print("Thanks for calling me")
print("This is the function 'f'")
print("I am calling 'g' now:")
g()
f()
得到以下输出:
This is the function 'f'
I am calling 'g' now:
Hi, it's me 'g'
Thanks for calling me
又一个使用了return的例子
def temperature(t):
def celsius2fahrenheit(x):
return 9 * x / 5 + 32
result = "It's " + str(celsius2fahrenheit(t)) + " degrees!"
return result
print(temperature(20))
输出:
It's 68.0 degrees!
函数作为参数
单看上面的例子,好像没有锤子用.
但是结合python更加强大的可能性就会变得非常有用.
因为因为函数的参数是对象,函数也是对象.所以函数可以当作传递给函数.
另一种说法,函数的引用作为函数的参数.下面一个简单的例子
def g():
print("Hi, it's me 'g'")
print("Thanks for calling me")
def f(func):
print("Hi, it's me 'f'")
print("I will call 'func' now")
func()
f(g)
输出:
Hi, it's me 'f'
I will call 'func' now
Hi, it's me 'g'
Thanks for calling me
你可能对上面结果不满意,函数 ‘f’ 应该调用 ‘g’ 而不是 ‘func’.
我们需要知道func的真实名字是什么,为此,我们使用name,属性,像这样:
def g():
print("Hi, it's me 'g'")
print("Thanks for calling me")
def f(func):
print("Hi, it's me 'f'")
print("I will call 'func' now")
func()
print("func's real name is " + func.__name__)
f(g)
输出结果说明到底发生了什么
Hi, it's me 'f'
I will call 'func' now
Hi, it's me 'g'
Thanks for calling me
func's real name is g
又一个例子:
import math
def foo(func):
print("The function " + func.__name__ + " was passed to foo")
res = 0
for x in [1, 2, 2.5]:
res += func(x)
return res
print(foo(math.sin))
print(foo(math.cos))
输出结果:
The function sin was passed to foo
2.3492405557375347
The function cos was passed to foo
-0.6769881462259364
函数返回函数(Functions returning Functions)
函数的返回值同样是一个对象,所以函数也可以作为函数的返回值.
def f(x):
def g(y):
return y + x + 3
return g
nf1 = f(1)
nf2 = f(3)
print(nf1(1))
print(nf2(1))
输出结果:
5
7
如果我们想创建一个多项式’工厂’函数,我们开始写这样一个版本,最高二次的.
python的实现可以像这样:
def polynomial_creator(a, b, c):
def polynomial(x):
return a * x**2 + b * x + c
return polynomial
p1 = polynomial_creator(2, 3, -1)
p2 = polynomial_creator(-1, 2, 1)
for x in range(-2, 2, 1):
print(x, p1(x), p2(x))
我们可以实现任意次多项式,像这样:
def polynomial_creator(*coefficients):
""" coefficients are in the form a_0, a_1, ... a_n
"""
def polynomial(x):
res = 0
for index, coeff in enumerate(coefficients):
res += coeff * x** index
return res
return polynomial
p1 = polynomial_creator(4)
p2 = polynomial_creator(2, 4)
p3 = polynomial_creator(2, 3, -1, 8, 1)
p4 = polynomial_creator(-1, 2, 1)
for x in range(-2, 2, 1):
print(x, p1(x), p2(x), p3(x), p4(x))
例如,函数p3就像这样:
上面输出结果如下:
(-2, 4, -6, -56, -1)
(-1, 4, -2, -9, -2)
(0, 4, 2, 2, -1)
(1, 4, 6, 13, 2)
如果你想进一步了解多项式,请参阅Polynomials
一个简单的修饰器(a Simple decorator)
现在我们准备好来定义我们的第一个修饰器了:
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
func(x)
print("After calling " + func.__name__)
return function_wrapper
def foo(x):
print("Hi, foo has been called with " + str(x))
print("We call foo before decoration:")
foo("Hi")
print("We now decorate foo with f:")
foo = our_decorator(foo)
print("We call foo after decoration:")
foo(42)
你可以看到下面的输出结果,就知道发生了什么.
在’foo = our_decorator(foo)’修饰之后,foo现在指向的是”function_wrapper”,foo将在function_wrapper里买你调用,在调用前后,分别有两条额外print语句
We call foo before decoration:
Hi, foo has been called with Hi
We now decorate foo with f:
We call foo after decoration:
Before calling foo
Hi, foo has been called with 42
After calling foo
python里面常用修饰器语法(The syntax for Decorators in python)
修饰器通常不是像上面的例子那样使用,虽然foo = our_decorator(foo)这样的形式容易理解容易掌握.就像上面例子.但是存在有一个问题,方法foo有先后有不同版本在同一个程序里面.
现在我们要做一个正确的修饰器声明,在函数签名上一行声明,@后面接函数名称
重写上面例子
foo = our_decorator(foo)
写成这样
@our_decorator
但是这一行要直接放在被声明函数前面.像这样:
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
func(x)
print("After calling " + func.__name__)
return function_wrapper
@our_decorator
def foo(x):
print("Hi, foo has been called with " + str(x))
foo("Hi")
我们可以用’our_decorator’ 修书任意其他有一个参数的函数,下面例子,稍微改变了一下包裹函数,更加方便的看到程序结果:
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
res = func(x)
print(res)
print("After calling " + func.__name__)
return function_wrapper
@our_decorator
def succ(n):
return n + 1
succ(10)
输出结果:
Before calling succ
11
After calling succ
我们也可以修饰第三方函数,从其他模块里面导入的函数,这种情况就不能用’@’语法了
rom math import sin, cos
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
res = func(x)
print(res)
print("After calling " + func.__name__)
return function_wrapper
sin = our_decorator(sin)
cos = our_decorator(cos)
for f in [sin, cos]:
f(3.1415)
得到如下结果:本文来源:Seakee.top. 未经允许不得转载.
Before calling sin
9.265358966049026e-05
After calling sin
Before calling cos
-0.9999999957076562
After calling cos
总结来说,修饰器使用用来改变函数,方法或者类的可调用的函数.原来的的对象作为参数传递给修饰器,修饰器返回修改后的对象,比如修饰后函数依然绑定给原来函数的名称.
上面的’function_wrapper’适合一个参数的函数,我定义一个通用版的’function_wrapper’,可以有任意的参数:
from random import random, randint, choice
def our_decorator(func):
def function_wrapper(*args, **kwargs):
print("Before calling " + func.__name__)
res = func(*args, **kwargs)
print(res)
print("After calling " + func.__name__)
return function_wrapper
random = our_decorator(random)
randint = our_decorator(randint)
choice = our_decorator(choice)
random()
randint(3, 8)
choice([4, 5, 6])
结果如下:
Before calling random
0.16420183945821654
After calling random
Before calling randint
8
After calling randint
Before calling choice
5
After calling choice
修饰器使用案例(User Case for Decorators)
检查参数
在递归那一节我们介绍了阶乘,我们设计足够简单的函数,保证纯粹的思想.因此我们没有做参数检查,如果有人用负数或者浮点型数作为参数,函数就会死循环.
我们用修饰器来保证函数参数正确.
def argument_test_natural_number(f):
def helper(x):
if type(x) == int and x > 0:
return f(x)
else:
raise Exception("Argument is not an integer")
return helper
@argument_test_natural_number
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n-1)
for i in range(1,10):
print(i, factorial(i))
print(factorial(-1))
记录函数调用次数
下面例子例子用修饰器记录函数调用的次数,我们的函数只能有一个参数
def call_counter(func):
def helper(x):
helper.calls += 1
return func(x)
helper.calls = 0
return helper
@call_counter
def succ(x):
return x + 1
print(succ.calls)
for i in range(10):
succ(i)
print(succ.calls)
输出:
0
10
注意,上面修饰器例子,只能有一个参数.我可以使用 *args 和 **kwargs
来标记任意参数函数:
def call_counter(func):
def helper(*args, **kwargs):
helper.calls += 1
return func(*args, **kwargs)
helper.calls = 0
return helper
@call_counter
def succ(x):
return x + 1
@call_counter
def mul1(x, y=1):
return x*y + 1
print(succ.calls)
for i in range(10):
succ(i)
mul1(3, 4)
mul1(4)
mul1(y=3, x=2)
print(succ.calls)
print(mul1.calls)
输出
0
10
3
有参数的修饰器
我定义了两个修饰器,代码如下:
def evening_greeting(func):
def function_wrapper(x):
print("Good evening, " + func.__name__ + " returns:")
func(x)
return function_wrapper
def morning_greeting(func):
def function_wrapper(x):
print("Good morning, " + func.__name__ + " returns:")
func(x)
return function_wrapper
@evening_greeting
def foo(x):
print(42)
foo("Hi")
这两个修饰器基本相同.我们想加一个参数,来定制这个greeting这个修饰器.我们在上面修饰器加一个函数来实现这个.我可以以更加极客的方式来说”Good morning”:
def greeting(expr):
def greeting_decorator(func):
def function_wrapper(x):
print(expr + ", " + func.__name__ + " returns:")
func(x)
return function_wrapper
return greeting_decorator
@greeting("καλημερα")
def foo(x):
print(42)
foo("Hi")
输出:
καλημερα, foo returns:
42
如果你不想用@语法,可以直接调用函数:
def greeting(expr):
def greeting_decorator(func):
def function_wrapper(x):
print(expr + ", " + func.__name__ + " returns:")
func(x)
return function_wrapper
return greeting_decorator
def foo(x):
print(42)
greeting2 = greeting("καλημερα")
foo = greeting2(foo)
foo("Hi")
```shell
输出结果和上面一直:
</code></pre>
καλημερα, foo returns:
42
<pre><code class="">当然我们可以不用多余的"greeting2",我们可以直接将"greeting("καλημερα")"返回结果用到"foo"上:
```python
foo=greeting("καλημερα")(foo)
使用fooctools里包裹函数(Using wraps from functools)
我们定义修饰器时没有考虑这些属性:
- name(函数的名称)
- doc(函数的文档)
- module(函数定义所在的模块)
下面这个修饰器保存在文件greeting_decorator.py里面:
def greeting(func):
def function_wrapper(x):
""" function_wrapper of greeting """
print("Hi, " + func.__name__ + " returns:")
return func(x)
return function_wrapper
在如下程序里面使用:
from greeting_decorator import greeting
@greeting
def f(x):
""" just some silly function """
return x + 4
f(10)
print("function name: " + f.__name__)
print("docstring: " + f.__doc__)
print("module name: " + f.__module__)
得到如下结果:
Hi, f returns:
function name: function_wrapper
docstring: function_wrapper of greeting
module name: greeting_decorator
我们可以保留原有属性,如果在修饰器里逐一赋值的话.代码修改成这样:
def greeting(func):
def function_wrapper(x):
""" function_wrapper of greeting """
print("Hi, " + func.__name__ + " returns:")
return func(x)
function_wrapper.__name__ = func.__name__
function_wrapper.__doc__ = func.__doc__
function_wrapper.__module__ = func.__module__
return function_wrapper
在我们的免程序里面,我们修改导入语句:
from greeting_decorator_manually import greeting
现在我们得到正确的结果:
Hi, f returns:
function name: f
docstring: just some silly function
module name: __main__
幸运的是我们不用加这段代码到我们的修饰器里面去,我们可以使用functools里面的warps来修饰它:
from functools import wraps
def greeting(func):
@wraps(func)
def function_wrapper(x):
""" function_wrapper of greeting """
print("Hi, " + func.__name__ + " returns:")
return func(x)
return function_wrapper
类修饰器(Classes instead of Functions)
call方法
之前我都使用函数修饰器.在定义一个类修饰器之前,我们需要了解类的call方法.我们早就知道,修饰器是一个可调用的对象.函数就是可调用的对象.许多python程序员可能不知道,类也可以定义成可调用对象.如果定义了,call方法将会被调用,像函数一样.
class A:
def __init__(self):
print("An instance of A was initialized")
def __call__(self, *args, **kwargs):
print("Arguments are:", args, kwargs)
x = A()
print("now calling the instance:")
x(3, 4, x=11, y=10)
print("Let's call it again:")
x(3, 4, x=11, y=10)
输出如下:
An instance of A was initialized
now calling the instance:
Arguments are: (3, 4) {'x': 11, 'y': 10}
Let's call it again:
Arguments are: (3, 4) {'x': 11, 'y': 10}
我们可以用类来重写fibonacci函数:
class Fibonacci:
def __init__(self):
self.cache = {}
def __call__(self, n):
if n not in self.cache:
if n == 0:
self.cache[0] = 0
elif n == 1:
self.cache[1] = 1
else:
self.cache[n] = self.__call__(n-1) + self.__call__(n-2)
return self.cache[n]
fib = Fibonacci()
for i in range(15):
print(fib(i), end=", ")
输出:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
用类来做修饰器(Using a Class as a Decorator)
我们重写下面修饰器用类来实现:
def decorator1(f):
def helper():
print("Decorating", f.__name__)
f()
return helper
@decorator1
def foo():
print("inside foo()")
foo()
类的实现:
class decorator2:
def __init__(self, f):
self.f = f
def __call__(self):
print("Decorating", self.f.__name__)
self.f()
@decorator2
def foo():
print("inside foo()")
foo()
未经允许禁止转载!!!!