【图文并茂】一文看懂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. 装饰器感想
装饰器写到这里也看了很多教程。最主要的就是要理解最基础的东西,这个一上来看装饰器肯定要迷糊死。
基础的就是大概自己写下来感觉有这几个点吧。
- Python函数名其实就是个变量,函数体是是变量的内容。test这样就是个变量名,test()这样算调用。
- 首先要理解嵌套函数,起码看这么五六遍,自己写个五六遍,隔几天写个几遍。(一周我就全忘完了
- 多看网上的例子,大神写的装饰器,然后自己写下来的同时,把自己的思路也用文字写下来。
共有评论(0)