解惑,从新认识python装饰器

概念

python有两种装饰器:

  • 函数装饰器(function decorators)
  • 类装饰器(class decorators)

基础知识

  1. 首先函数名是对函数的引用,我们可以给同一个函数多重命名:
>>> def succ(x):
...     return x + 1
... 
>>> successor = succ
>>> successor(10)
11
>>> succ(10)
11
>>> del succ
>>> successor(10)
11
>>> 
  1. 函数内部可以定义函数:
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
'''

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!
'''
  1. 函数可以作为参数传给另外一个函数:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 09:49:39
#Name:functionAsParameters.py
#Version:V1.0

# 定义一个函数g
def g():
    print("Hi, it's me 'g'")
    print("Thanks for calling me")

# 定义一个函数f,参数为函数
def f(func):
    print("Hi, it's me 'f'")
    print("I will call 'func' now")
    func()
    
# 将函数g作为参数传递给函数f
f(g)

你可能不太满意上述程序的输出, 函数f应该调用'g'而不是'func',为了达到这一点,我们使用属性__name__:

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 09:57:08
#Name:functionAsParameters001.py
#Version:V1.0

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 name is " + func.__name__)

f(g)

另外一个例子

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 10:01:11
#Name:functionAsParameters002.py
#Version:V1.0

import math

def foo(func):
    print("The function " + func.__name__ + " was pass to foo.")
    res = 0
    for x in [1, 2, 2.5]:
        res += func(x)
    return res

print(foo(math.sin))
print(foo(math.cos))
  1. 函数可以返回函数
#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 10:14:17
#Name:functionReturnFunction.py
#Version:V1.0

def f(x):
    def g(y):
        return y+x+3
    return g

nf1 = f(1)
nf2 = f(3)

print(nf1(1))
print(nf2(1))

定义一个二次方程:

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 10:19:41
#Name:functionReturnFunction001.py
#Version:V1.0
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))

更为复杂的多元方程:

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 10:25:38
#Name:functionReturnFunction002.py
#Version:V1.0

def polynomial_creator(*coefficients):
    '''
    cofficients 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))

一个简单的装饰器

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 10:37:12
#Name:aSimpleDecorator.py
#Version:V1.0

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)

python装饰器的标准语法

python装饰器的标准语法和我们上例介绍的不太一样,尽管foo = our_decorator(foo)更容易记忆和理解.在上例子中在同一个程序里我们定义了两个版本的foo函数,一个是装饰前的一个是装饰后的, 因此python里一般不这样定义装饰器.
为了实现装饰器,python中使用在'@'后接包装函数名的方式来定义装饰器.

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 10:56:10
#Name:syntaxForDecorators.py
#Version:V1.0

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

# We can decorate every other function which takes one parameter with our decorator 'our_decorator'.
@our_decorator
def succ(n):
    return n + 1

succ(10)

# It is also possible to decorate third party functions
from math import sin,cos
def our_decorator1(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_decorator1(sin)
cos = our_decorator1(cos)

for f in [sin, cos]:
    f(3.1415)

总结: 我们可以说,Python中的装饰器是一个可调用的Python对象,用于修改函数、方法或类定义。将要修改的原始对象作为一个参数传递给装饰者。装饰器返回一个修改过的对象,例如一个修改后的函数,该函数绑定到装饰器定义中使用的func名称。

在我们前面定义的装饰器只能为只有一个参数的函数服务,我们下面的例子将会展示更为广泛的适用:

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 11:27:11
#Name:syntaxForDecorators001.py
#Version:V1.0

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

python装饰器的应用场景

使用装饰器检查传参

下面的程序使用一个装饰功能,确保传递给函数的参数因子是一个正整数:

#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 11:44:58
#Name:checkingArguments.py
#Version:V1.0
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)

多参数实例:

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 11:58:57
#Name:countingFunctionCalls001.py
#Version:V1.0

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 mull(x, y=1):
    return x*y + 1

print(succ.calls)
for i in range(10):
    succ(i)

mull(3,4)
mull(4)
mull(y=3, x=2)

print(succ.calls)
print(mull.calls)

带参数的装饰器

我们先来看看下面的例子:

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 14:44:43
#Name:decoratorsWithParameters1.py
#Version:V1.0

def evening_greeting(func):
    def function_wrapper(x):
        print("Good evening, " + func.__name__ +" returns:")
        func(x)
    return function_wrapper

def moning_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")

上例中两个装饰器基本相同,我们也可以通过给装饰器传递参数使得两个装饰器合二为一:


#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 14:58:38
#Name:decoratorsWithParameters2.py
#Version:V1.0

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("Good morning, ")
def foo(x):
    print(42)

foo("Hi")

这种方式相当于:

greeting2 = greeting("Good morning, ")
foo = greeting2(foo)

或者:

foo = greeting("Good morning, ")(foo)

导入装饰器

如果装饰器是从其他模块导入的话, 将会失去以下属性:

  • __name__ name of the function
  • __doc__ the docstring
  • __module__ the module in which the function is defined

首先我们在'greeting_decorator.py'中定义一个装饰器

#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 15:15:48
#Name:greeting_decorator.py
#Version:V1.0

def greeting(func):
    def function_wrapper(x):
        """ function_wrapper of greeting """
        print("Hi, " + func.__name__ + " returns:")
    return function_wrapper

然后我们从另一个程序中导入这个装饰器:


#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 15:19:49
#Name:call_greeting_decorator.py
#Version:V1.0

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

输出的结果如下:

fbo@fbo-virtual-machine:~/tmp/py$ python3 call_greeting_decorator.py 
Hi, f returns:
function name: function_wrapper
docstring:  function_wrapper of greeting 
module name: greeting_decorator

这个并不是我们想要得到的结果,如果要得到想要的结果,我们必须对装饰函数做一些更改,保存为greeting_decorator_manually.py:

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 15:27:53
#Name:greeting_decorator_manually.py
#Version:V1.0

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

幸运的是我们并不需要做这些工作,简单的实现方式是从模块functools导入装饰器wraps,例如:

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 15:15:48
#Name:greeting_decorator.py
#Version:V1.0

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

类装饰器

__call__方法

在介绍类装饰器之前我们先来介绍以下类的__call__方法;我们已经提到过装饰器就是一个把函数当做传参的简单可调用对象.函数就是一个可调用对象,我们也可以把类定义胃可调用对象,__call__方法可以使类的实例像函数一样被调用:

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 16:05:51
#Name:callClass.py
#Version:V1.0

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)

调用__call__方法使用类来实现斐波那契数列:


#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 16:10:22
#Name:callClass1.py
#Version:V1.0

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 = ", ")

print()

使用类作为装饰器

#!/usr/bin/env python3                                                                              
#Author:fbo
#Time:2017-12-06 16:25:20
#Name:classDecorator.py
#Version:V1.0

class decorator:
    def __init__(self,f):
        self.f = f
    def __call__(self):
        print("Decorating", self.f.__name__)
        self.f()

@decorator
def foo():
    print("inside foo()")

foo()

你可能感兴趣的:(解惑,从新认识python装饰器)