...

【图文并茂】一文看懂Python闭包&装饰器

压在Python新手的三座大山,迭代器「Iterator」,生成器「Generator」,装饰器「Decorator」。

感觉装饰器可能是我个人最难理解的,所以我总结一下。尽量采用略懂皮毛的逻辑思维来写一下好了。
想知道装饰器,就要先了解闭包「Closure」。

1. 闭包 Closure

Python还有Javascript这种高级脚本语言都有的感觉。简言之就是。

  • 函数里面套一个函数
  • 共享外层函数变量

具体用起来就感觉是一个函数,可以通过参数不同,生成大概相似的多个函数进行调用。

代码如下

# closure  
def outfun(outNum):  
    print('---start outfun---')  

    def infun(inNum):             
        print('---start infun---')  
        print(outNum + inNum)  
        print('---end infun---')  

    print('---end outfun---')  
    return infun  

# 这时候ret已经固定成内部的函数  
# 直接打印出来的结果就是ret指向的内存地址  
ret = outfun(10)  
print(ret)  
# ---start outfun---  
# ---end outfun---  
# <function outfun.<locals>.infun at 0x10cf6cc20>  


# 这时候才是调用ret函数,但是由于缺少一个参数报错  
ret = outfun(10)  
print(ret())  
# ---start outfun---  
# ---end outfun---  
# TypeError: infun() missing 1 required positional argument: 'inNum'  

# 完整的调用  
ret = outfun(10)  
ret(20)  
# ---start outfun---  
# ---end outfun---  
# ---start infun---  
# 30  
# ---end infun---  

# 重复调用  
ret = outfun(10)  
ret(20)  
ret(120)  
ret(230)  
# ---start outfun---  
# ---end outfun---  
# ---start infun---  
# 30  
# ---end infun---  
# ---start infun---  
# 130  
# ---end infun---  
# ---start infun---  
# 240  

使用闭包的好处就是少了参数的传递。
原来2个数字相加需要2个参数,这里可以直接使用一个参数。

2. 装饰器 Decorator

如果你写好了一个函数,这时候需要新增一个检查代码的需求。
那么你会怎么做?

看图引出思路。

2.1 装饰器引入思路

2.2 装饰器原理解析

可以看出来在Python里最好的实现就是通过装饰器。

那么装饰器到底是怎么搞出来的?原理呢?

最后的最后就是装饰器了

以上的步骤就是,增加功能=>闭包=>装饰器=>装饰器实现
整个思考的过程。如果一开始就理解装饰器可能会比较困难,我这里拆分了一下。

3. 装饰器进阶

3.1 多个装饰器

如果装饰器2个一起来,那么顺序是什么呢?

# 多个装饰器  
def makeBold(func):  
    print('--1 out--')  
    def wrapped():  
        print('--1 in--')  
        return '<b>' + func() + '</b>'  
    return wrapped  

def makeItalic(func):  
    print('--2 out--')  
    def wrapped():  
        print('--2 in--')  
        return '<i>' + func() + '</i>'  
    return wrapped  

@makeBold   # tag = makeBold(tag)  
@makeItalic # tag = makeItalic(tag)  
def tag():  
    print('--3--')  
    return 'hello world'  

ret = tag()  
print(ret)  

"""  
可以看出来输出结果和执行结果不一样。  
那么这是怎么来的呢?  
"""  
# --2 out--  
# --1 out--  
# --1 in--  
# --2 in--  
# --3--  
# <b><i>hello world</i></b>  

多个装饰器执行思路应该是这样的,这里写一个大概的执行顺序思路。

如果说初级阶段无法理解上面的过程,可以简单理解成内部就近原则进行装饰。装的时候和拆的时候不一样。装的时候是从上倒下,拆的时候从内至外。

装饰器执行到了@这里,就一定会开始运行函数,因为这就是调用函数不用等到调用才进行装饰
调用从上向下,装饰从下到上。

3.2 有参数的装饰器

3.2.1 演变

上面讨论的都是装饰没有参数的函数,但是实际开发中没有参数的函数很少。
下面这3段代码就可以看一下有参数的装饰器是怎么演变过来的。

"""  
1,直接在调用函数参数里面写  
    肯定是错误的,这样infunc根本没有参数进入,看报错信息就可以知道。  
"""  
def outFunc(func):  
    print('start outfunc')  
    def inFunc():  
        print('--infunc')  
        func()  
    return inFunc  

@outFunc  
def test(number):  
    print('number is ',number)  
test(5)  
# TypeError: inFunc() takes 0 positional arguments but 1 was given  

"""  
2,inFunc()里面加上参数,会发现也是错误的  
    报错信息提醒我们test()缺少了参数,其实这个test()就是  
    test = outFunc(test)  
    调用的时候func里面缺少的参数  
"""  
def outFunc(func):  
    print('start outfunc')  
    def inFunc(number):  
        print('--infunc')  
        func()  
    return inFunc  

@outFunc  
def test(number):  
    print('number is ',number)  
test(5)  
#TypeError: test() missing 1 required positional argument: 'number'  

"""  
3,这次给inFunc()和func()都加上参数  
    成功了!  
"""  
def outFunc(func):  
    print('start outfunc')  
    def inFunc(number):  
        print('--infunc')  
        func(number)  
    return inFunc  

@outFunc  
def test(number):  
    print('number is ',number)  
test(5)  
# start outfunc  
# --infunc  
# number is  5  

3.2.2 不定长参数

写代码的时候,函数的参数又不一定是一直就固定的,后期可能会变。但每次都要修改装饰器这是不可能的。于是就可以使用arg(tuple)s,kwagrs(dic)涵盖掉所有的参数。

def outFunc(func):  
    print('start outfunc')  
    def inFunc(*agrs, **kwargs):  
        print('--infunc')  
        func(*agrs, **kwargs)  
    return inFunc  

@outFunc  
def test(number):  
    print('number is ',number)  
test(5)  

@outFunc  
def test2(a, b, c):  
    print('sum number is ',(a+b+c))  
test2(5,6,7)  

# start outfunc  
# --infunc  
# number is  5  
# start outfunc  
# --infunc  
# sum number is  18  

3.3 有返回值的装饰器

"""  
1,关于有返回值的装饰器 打印出来的是None,因为返回值没有被接收  
"""  
def checkFunction(func):  
    print('checkfunction start')  
    def inFuction():  
        print('start infunction')  
        func()  
        print('end function')  
    return inFuction      

@checkFunction  
def testFucntion():  
    print('testfunction start')  
    return 'ok'  
ret = testFucntion()  
print(ret)  

# checkfunction start  
# start infunction  
# testfunction start  
# end function  
# None  

"""  
2,将返回值接收之后进行return可以正确输出  
"""  
def checkFunction(func):  
    print('checkfunction start')  
    def inFuction():  
        print('start infunction')  
        ret = func()  
        print('end function')  
        return ret  
    return inFuction  


@checkFunction  
def testFucntion():  
    print('testfunction start')  
    return 'ok'  
ret = testFucntion()  
print(ret)  

# checkfunction start  
# start infunction  
# testfunction start  
# end function  
# ok  

3.4 通用装饰器

按照上面写的流程,每次感觉又需要加参数,又要弄返回值。这样一步步思考实在太麻烦了。
这里有一个通用的装饰器,可以适应上面所有的情况。
其实没有就是不定长参数+有返回值的综合体就可以

def normalDec(func):  
    def wrapped(*args, **kwargs):  
        print('start wrapped')  
        ret = func(*args, **kwargs)  
        print('end wrapped')  
        return ret  
    return wrapped  

@normalDec  
def testDec(a, b):  
    return('a + b is %d' %(a+b))  

ret = testDec(5,6)  
print(ret)  

# 基本的骨架就是这样的一个内包结构  
def normalDec(func):  
    def wrapped(*args, **kwargs):  
        ret = func(*args, **kwargs)  
        return ret  
    return wrapped  

4. 装饰器里有参数

普通的装饰器没有参数,如果装饰器里带有参数的话。就要在外面再加一层闭包。
感觉就很像是装饰器通过外面一层函数把变量传递到里面去。
这属于稍微高级一点,难度大一点的闭包用法了。

"""  
1,实现很简单。就是在多家一层外包,return到里面的函数  
"""  
def paraDec(*agrs):  
    def paraDec_in(func):  
        print('start out')  
        def wrapped():  
            print('start in %s' %agrs)  
            func()  
            return 'ok'  
        return wrapped  
    return paraDec_in  

@paraDec('hello')  
def test():  
    print('start test')  
test()  
# start out  
# start in hello  
# start test  

"""  
2,稍微在复杂一点的应用进去  
进入去不同的参数会发现有不同的步骤进行装饰  
"""  
def paraDec(agrs):  
    def paraDec_in(func):  
        print('start out')  
        def wrapped():  
            if agrs == 'login':  
                print('start in %s' %agrs)  
            elif agrs == 'logout':  
                print('start in %s' %agrs)  
                func()  
            return 'ok'  
        return wrapped  
    return paraDec_in  

@paraDec('login')  
def test():  
    print('start login')  
test()  

print('------')  

@paraDec('logout')  
def test2():  
    print('start logout')  
test2()  

原理大概是这样的。
先运行@paraDec('logout')调用最外面的函数,然后返回值就给了里面的2层函数。
我目前个人的理解就是,加了一层函数就是就是传递了个变量进去,其他没有任何屁用。

5. 装饰器感想

装饰器写到这里也看了很多教程。最主要的就是要理解最基础的东西,这个一上来看装饰器肯定要迷糊死。
基础的就是大概自己写下来感觉有这几个点吧。

  1. Python函数名其实就是个变量,函数体是是变量的内容。test这样就是个变量名,test()这样算调用。
  2. 首先要理解嵌套函数,起码看这么五六遍,自己写个五六遍,隔几天写个几遍。(一周我就全忘完了
  3. 多看网上的例子,大神写的装饰器,然后自己写下来的同时,把自己的思路也用文字写下来。

共有评论(0)

登陆即可评论哦