再谈python装饰器

人生苦短,我用Python

__ 文:郑元春__


1. 一切都是参数

首先,在python中什么都可以传递,不只是一般的变量,对象,python还具有面向函数编程的思想,所以你写的任何的函数也可以当做是参数传递给另一个函数。只要记住,在python中什么都能传递。

函数的作用当然是实现功能了,所以作为另一个函数的参数的时候就把当前函数的功能带过去了(只要调用就行,和一般调用的区别就是这是软编码的,并不是硬调用的),同时还可以增添一些其他的功能。这就是在不改变原来函数的情况下,我们只需要编写额外的功能在另一个函数中,实现动态“对功能打补丁”的效果。


2. 一般的调用

一般的调用就是硬编码调用,在另一个函数中直接实现对当前函数的调用,这种调用只能实现一对一的调用。

def func1():
    print ("func1")

def func2():
    print("before")
    func1()
    print("after")

我们在func2函数中调用了func1函数,但这是硬编码的,当我们有了另外一个函数func11的时候,我们要想实现func2的之前和之后输出的时候,我们还得写另外一个函数来包装,所以这种方式是一个比较“硬”的编码方式。


3. 面向函数编程思想

既然在python中可以将函数都当做参数传递,那么我们可以尝试着做如下的改变。

def func1():
    print("func1")

def func2():
    print("func2")

def wrapper(func):
    print("before")
    func()
    print("after")
    return func  #这里的return很重要,要不然调用一次wrapper之后原先的func就成了空了
func1=wrapper(func1)
func2=wrapper(func2)
f=wrapper(func1)    #这里f就是func1函数

这里编写了wrapper函数之后,直接将需要“打补丁”的函数当做参数传递进去就可以,注意这里的wrapper最后将传递进来的函数又return回去了,也就是说wrapper只做了增添功能的动作,最后并返回了原先的函数,这样我们使用func1=wrapper(func1)操作的时候,func1还是原来的函数并没有做任何的改变,如果最后没有这一句return的话,那么使用func1=wrapper(func1)就对func1进行了重写,重写的结果是func1变成了空,这不是我们想要的结果,所以一定要记住这个返回语句。

我们可以这样写,但是这样写并不能体现出python的牛逼来,所以python中有一个语法糖是这么实现的。我们称它为装饰器(decorator)。


4. 神奇的decorator

python使用装饰器将上面的函数重写变成了另一种十分简洁的方式:

def wrapper(func):
    print("before")
    func()
    print("after")
    return func

@wrapper
def f()
    print("call f")

如果你将上面的脚本保存成python文件并运行你可以看到输出以下内容:

before
call f
after

其实装饰器此时会将代码转换成:f=wrapper(f),完成的实际上函数的重写(增添了wrapper的功能),为了保持原来的函数的本质,此时一定不要忘记在wrapperreturn原来的函数。但是你会发现上面的脚本的输出方式并不是我们预计的输出结果,脚本首先自动输出了wrapper的结果,当我们显示调用的时候却只保持了原来函数的输出。我们分析代码,关键字@就是一个函数重写语句,相当于f=wrapper(f)所以此时调用了wrapper函数并在wrapper内部调用了f函数。此时完成了f的重写,还是原来的函数功能。

我们希望函数在加了装饰器之后能够保持住“打补丁”之后的升级功能,并不是指调用一次。一个自然而然的做法就是将wrapper函数在封装一层,函数重写的时候并不是重写的原来的函数,而是“打补丁”之后的函数,返回的是一个函数句柄,如果我们不去显示调用的话,不会执行任何功能。


5. 第一个具有实际功能的decorator

我们将装饰器再封装一层。返回的是真实功能的函数句柄。这样既能保持升级后的函数所有功能,还能防止自动执行封装器。

def wrapper(func):
    def inner():
        print("before")
        func()
        print("after")
    return inner

#这里相当于 f=wrapper(f),此时f重写为inner的函数句柄
@wrapper
def f():
    print("call f")

f()  #如果不显示的执行的话,装饰器并没有任何执行过程

这样上面写的封装器在内部又封装了一层,返回是一个inner函数的句柄,只要不显示的调用就不会执行。

同时,多个装饰器还可以叠加使用

def wrapper1(func):
    def inner():
        print("before wrapper 1")
        func()
        print("after wrapper 2")
    return inner

def wrapper(func):
    def inner():
        print("before wrapper 2")
        func()
        print("after wrapper 2")
    return inner

@wrapper1
@wrapper2
def f():
    print("call f")

f()

[out]:before wrapper 1
      before wrapper 2
      call f
      after wrapper 2
      after wrapper 1

6. 带参数函数的装饰器

上面的装饰器的例子是最简单的例子,并没有牵扯到任何的参数信息。但是一般的函数都是带着参数的,让我们看看带参函数怎么写装饰器。

首先我们分析,之前的wrapper函数返回的是inner函数的函数句柄,inner函数会在装饰器执行的时候重写到实际的函数中,所以说可以将原先函数的参数交给inner函数来作为传递的桥梁。

def wrapper(func):
    def inner(a,b):
        print("before wrapper")
        func(a,b)
        print("after wrapper")
    return inner

@wrapper
def f(a,b):
    print ("call f: a+b=%d"% (a+b) )

f(2,3)

上面的装饰器相当于f=wrapper(f)=inner,然后可以将参数再加进去,就"相当于"参数传递了。


7. 参数个数不确定

有的时候我们在写装饰器之前是不知道将要装饰的函数到底有几个参数的,所以就不能使用基于位置的参数表示方式,我们使用(*args, **kwargs)来自动适应变参和命名参数

#coding:utf-8
def wrapper(func):
    def inner(*args, **kwargs):
        print("before %s"%func.__name__)
        result=func(*args,**kwargs)
        print("result of %s is %d"%(func.__name__,result))
    return inner

@wrapper
def f1(a,b):
    print("call f1")
    return a+b
@wrapper
def f2(a,b,c):
    print("call f2")
    return a+b+c

f1(1,2)
f2(2,3,4)

你可能感兴趣的:(再谈python装饰器)