解惑,从新认识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()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容

  • 〇、前言 本文共108张图,流量党请慎重! 历时1个半月,我把自己学习Python基础知识的框架详细梳理了一遍。 ...
    Raxxie阅读 18,790评论 17 410
  • 本文为《爬着学Python》系列第四篇文章。从本篇开始,本专栏在顺序更新的基础上,会有不规则的更新。 在Pytho...
    SyPy阅读 2,452评论 4 11
  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 1,401评论 0 6
  • Python进阶框架 希望大家喜欢,点赞哦首先感谢廖雪峰老师对于该课程的讲解 一、函数式编程 1.1 函数式编程简...
    Gaolex阅读 5,468评论 6 53
  • 老王坐在桌子旁,有所思地望着这覆着铜锈的镜子。 老王知道这面镜子价值万元,自从它从院中的那口枯井里被意外挖出时,第...
    索风阅读 235评论 0 2