[译]python 修饰器 (Decorators)


简介

修饰器应该是python语言里面最漂亮最强大的设计.同时也被认为是难学的.准确来说,使用修饰器是简单,但是设计修饰器是复杂的,尤其是你没有足够的修饰器和函数式编程的相关经验时.本文来源:Seakee.top. 未经允许不得转载.

虽然底层设计理念相同,但是我们还是有两种不同的修饰器:

  • 函数修饰器(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++ 程序员来书是一个完全陌生的概念:本文来源:Seakee.top. 未经允许不得转载.

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))

输出结果:本文来源:Seakee.top. 未经允许不得转载.

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

如果我们想创建一个多项式’工厂’函数,我们开始写这样一个版本,最高二次的.
p(x)=a*x^2+b*x+c
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

有参数的修饰器

我定义了两个修饰器,代码如下:本文来源:Seakee.top. 未经允许不得转载.

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()

原文地址

未经允许禁止转载!!!!

发表评论

电子邮件地址不会被公开。 必填项已用*标注