升升的NotaBooka



路漫漫其修远兮
吾将上下而求索




深度学习python装饰器



Python装饰器是个很好玩的东西,我们一步一步来了解这个东西。

首先,有一个函数

首先,python的函数,不用多说了吧?最简单的例子如下:

def do():   #定义一个简单的函数
    print "I am doing something..."

然后要知道,一切皆对象

python中,一切皆是对象。函数也一样:

do2 = do  #把函数赋值给另一个变量,仍然可以直接使用
do2()

把函数赋值给另一个变量,仍然可以直接使用

切片的需求

现在有个新的需求,要在调用do这个函数的之前和之后,都要做一些事情。 也就是要让do这个函数,插入到一个逻辑的中间去。 也可以理解为“我们正在往面包里夹火腿”。

那我们代码可以这么写:

def new_do():
    print "Before : Do something ..."
    do()   #do处在一个逻辑的中间,这是一种切片的设计模式
    print "After : Do somthing ..."

到这里,之前的需求已经得到了解决。

代码的可复用

然而,事情没有那么简单。

现在我们有很多个与do类似的函数,do2、do3、do4,都需要用这同一个切片逻辑。难道我要重复定义new_do2、new_do3吗? 当然不是,我们可以这样让代码复用起来:

def new_do(func):
    print "Before : Do something ..."
    func()
    print "After : Do something ..."

new_do(do)
new_do(do1)
new_do(do2)

代码复用起来了,我们并没有多写代码,很赞。

如何更快速的更新到代码

工程师追求完美的脚步是永远不会停止的。 如果添加这个切片的需求,是一个后期的需求,而我的do、do1、do2、do3…等等的函数,已经分布在了我所有代码中的n个文件的m行了。 我是不是要把每个地方的函数调用都修改成这样呢:

new_do(do)  #原来是do(),如果有200处,我要处处修改

很显然,这并不是最优的方案。

毋庸置疑,最优的方案,肯定是不需要处处去修改的。 那我们就可以以这个为目标,来思考怎样可以不用处处修改。

原调用如果仍然是do(),同时还要做到new_do的逻辑,我们只能替换do的函数定义。 就像这样:

do = new_do(do)
do2 = new_do(do2)

这样一来,python的万物皆对象这一点特性就派上用场了。 new_do的定义就呼之欲出了:

def new_do(func):
    def do_with_logical():
        print "Before : Do something ..."
        func()
        print "After : Do something ..."
    return do_with_logical

do = new_do(do)
do2 = new_do(do2)

new_do将返回一个函数,这个函数,是封装过了函数do的添加了逻辑的新的函数。 我们在源头替换了do的定义,这样只需要替换do的定义就可以了。

装饰器

其实到了这里,装饰器就已经有了,new_do就是一个很典型的装饰器了。 就像是给一个函数加了一件装饰。因此叫装饰器。

但是这么写,是不是不用高端不够大气不够上档次? 没有几个语法糖,你好意思出来说自己是高级语言?

语法糖来了:

@new_do
def do():
    print "Do Something..."

是不是感觉很清爽很干净很高端很让不懂的人感觉高大上?好的,目的达到了。

函数带参数

函数带参数怎么办呢?这个也简单,返回的那个函数支持下参数不就好了?

def new_do(func):
    def do_with_logical(a, b):
        print "Before : Do something ..."
        func(a, b)
        print "After : Do something ..."
    return do_with_logical

@new_do
def do(a, b):
    print "In do ", a, b

do(1, 2)

搞定!

函数参数可变

然而,还是那句话,工程师追求完美的脚步是永远无法停止的。 如果do1、do2、do3的参数并不是那么的规整,而这些参数又与我们装饰器的逻辑无关。 理论上代码仍然是可复用的。 要屏蔽参数的影响,那我们装饰器返回的这个方法,首先得支持可变参数:

def new_do(func):
    def do_with_logical(*args, **xwargs):
        print "Before : Do something ..."
        func(*args, **xwargs)
        print "After : Do something ..."
    return do_with_logical

@new_do
def do(a, b):
    print "In do ", a, b

@new_do
def do1(a, b, c, d):
    print "In do1", a, b, c, d

do(1, 2)
do1(1, 2, 3, 4) 

装饰器带参数

好了,现在我们的装饰器已经渐渐完善起来了,还缺点什么呢? 我们装饰器的逻辑是固定的,是否可以让装饰器带上参数,这样逻辑就可以更灵活呢?

让我们一步步来分析。 原来,我们的逻辑是这样的:

@new_do
def do():
    print "Do Something ..."

这个写法其实是等同于:

def do():
    print "Do Somethins ..."

do = new_do(do)

现在我们想要给装饰器加参数,我们的写法就要变成这样:

do = new_do("args")(do)

根据上述的调用方法,我们可以来思考下装饰器的定义需要有哪些变化:

  • new_do(“args”)其实等同于曾经的new_do
  • 其他的逻辑,还是要遵从之前的定义

新的new_do定义,就变成了这样:

def new_do(module):
    def ori_new_do(func):
        def do_with_logical(*args, **xwargs):
            print "[%s]Before : Do something ..." % (module, )
            func()
            print "[%s]After : Do something ..." % (module, )
        return do_with_logical

    return ori_new_do

@new_do("LogModule")
def do():
    print "In do function"

@new_do("NewModule")
def do2():
    print "In do2 function"

其实原理与之前的是一样的,新的new_do(“module”)与之前的new_do是等价的。 只是多封装了一层,对参数处理之后,返回之前的new_do,接下来逻辑就跟之前一样了。

发散

装饰器可以带参数了,在python中“一切皆对象”又派上用场了,你可以把任何的东西传给装饰器,比如说,类。 感觉这是个应该是个很有意思的玩法,虽然我没在实际项目中试过。