02Python学习笔记之二.四【闭包、装饰器】2019-08-17

章节号 内容            
1图片格式(png) 宽度大于620px,保持高宽比减低为620px
1-1 应用
1-1-1 方法

第1章节  闭包

  • 1-1 闭包—解释

  ↓函数引用:

In [84]: def test():
    ...:     print("1111111111111111")
    ...:     

In [85]: test
Out[85]: <function __main__.test>

In [86]: id(test)
Out[86]: 140017952235032

In [87]: b=test

In [88]: b()
1111111111111111

  函数名就是一个指向函数块地址的变量,这个变量就可以赋值给别的变量。
  闭包:一个函数内部又定义一个函数,而且内部函数使用到了外部函数的变量(形式参数),则外部的函数的参数和内部的函数统一构成一个闭包
  作用:

def test(number):
    print(1)
    def testin():
        print(2)
        print(number+100)
    print(3)
    return testin
    
test(10000)
1
3

  ↑可以看到,如上的函数,内部的那个函数并未执行。为什么呢?因为明显只调用了test(),而没有哪个地方调用了testin()。


  ↑具体流程如上:
  1、python解释器运行到1的箭头处,得知这里是一个函数的定义,则把这一块代码的首地址和test绑定。直接跳转到2处,因为没有调用函数,函数体是不执行的。
  2、从2这里开始跳入函数体开始执行。
  3、执行箭头3的打印语句。
  4、遇到了内部的函数定义。为testin赋值。
  5、直接跳出内部函数的定义,来到箭头5,执行打印语句。
  6、执行返回语句。
  执行到了这里,test(10000)这一串字符,是否就代表了内部函数的引用呢?
  加上一对括号()测试一下:

def test(number):
    print(1)
    def testin():
        print(2)
        print(number+100)
    print(3)
    return testin
    
test(10000)()
1
3
2
10100

  ↑可以看到内部函数已经被调用了。

  关键点在于:

  里面的函数在调用时,保存了外部函数传入的参数。从某种程度上来说,调用内部函数的时候,外部函数已经执行完毕了,这个参数应该已经度过了自己的生命周期,但是因为这是一个闭包,所以这个参数还能被内部函数继续使用。

def test(number):
    print(1)
    def testin(number1):
        print(2)
        print(number+100+number1)
    print(3)
    return testin
    
test(10000)(1)

  ↑我们为内部函数再加上一个形式参数,则调用时候也要作相应改变。

1
3
2
10101

  小结:外部函数的参数是基数,内部函数的参数是变数
  特点:外部函数的返回值,是内部函数的一个引用。

  • 1-2 闭包—应用

def line(a,b):
    def fx(x):
        return a*x+b
    return fx

print(line(1,4)(3))

  来看这个闭包的应用,注意看内部的返回值,a*x+b,这其实就是一个斜截式的直线方程,求的是y值。f(x)=a*x+b

7

  ↑来看调用后的答案。
  当我第一次接触闭包的应用的时候,其实我内心是拒绝的。我们仔细来看这个调用:

print(line(1,4)(3))

  大致,我觉得这句代码就是,调用2次函数,传入3个参数。对不对?
  那么,这样做从表面来看,是否是多此一举呢?我们考虑用如下方式来达到相同目的:

def line(x,a,b):
    return a*x+b

print(line(3,1,4))
7

  是不是这样程序更清晰呢?
  但是,有这样一个问题,让我们从这方面来考虑:
  f(x)=a*x+b这个方程组,a和b值是常数,即我们计算这个方程的时候,对于不同的x值,a和b只需要确定一次就行了,其余的时候我们只用改变x的值,就能求得不同的y值。
  考虑到这个问题,如果我们在a和b值一定的情况下,计算多个x值对应的y,那么大概就是如下的样子:

print(line(1,1,4))
print(line(2,1,4))
print(line(3,1,4))
print(line(4,1,4))
print(line(5,1,4))
print(line(6,1,4))
#等等

  对了,你大概注意到了,1和4,是否没必要传递那么多次。有办法解决吗?想一想闭包。

def line(a,b):
    def fx(x):
        return a*x+b
    return fx


fx=line(1,2)

print(fx(1))
print(fx(2))
print(fx(3))
print(fx(4))
print(fx(5))
print(fx(6))

  是不是感觉,为了一个简洁,省力,真是无所不用其极呀!

第2章节 装饰器

  • 2-1 装饰器—工作原理

  ↓首先从这段代码起步

In [1]: def func():
   ...:     print("func")
   ...:     

In [2]: func
Out[2]: <function __main__.func>

In [3]: func()
func

In [7]: fun = lambda x:x+1

In [8]: fun(1)
Out[8]: 2

In [9]: fun=lambda x,y:x+5*y

In [10]: fun(1,2)
Out[10]: 11

  ↓然后是这段代码:

def func():
    print("func1")

def func():
    print("func2")

func()
func2

  ↑python从上到下解析,上面func的地址被下面的func覆盖了,所以执行的是最后一个。
  下面开始需求提出及分析思路,假设类似多个函数如下:

def func1():
    print("func1")

def func2():
    print("func2")
    
def func3():
    print("func4")

def func4():
    print("func4")
.
.
.

  后因程序需要,每个函数需要加入一个功能N,则我们如何解决?显而易见的方式为:

def func1():
    print("func1")
    功能N

def func2():
    print("func2")
    功能N    

def func3():
    print("func4")
    功能N

def func4():
    print("func4")
    功能N
.
.
.

  但是如果这样的函数有成百上千个,那么一是工作量太大,二是相同的冗余代码将大幅增多。有什么改进方式吗?可以考虑如下:

def function():
    功能N

def func1():
    print("func1")
    function()  

def func2():
    print("func2")
    function()  

def func3():
    print("func4")
    function()  

def func4():
    print("func4")
    function()  
.
.
.

  是不是感觉突然变得很美好?
  但是,编写代码应该遵循的原则是,能扩不改老旧不动。在函数里加了调用,某种意义上还是修改了代码,可能造成不可描述的问题。考虑如下改动:

def zsq(func):
    print("gong neng 1 diao yong")
    func()

def func1():
    print("func1")

zsq(func1)

  ↑写一个新函数,把功能N在新函数中实现,这个新函数有个参数,传递的是函数的引用,则可以在新函数中调用老函数。但是这样有一个问题,你调用的方式发生了改变:从调用func1变成了调用zsq(func1),这有什么问题呢???就是要大量修改原来代码中写的func1()!!!!!!
  思考解决办法。
  核心问题在哪里,那就是我们要完成一个等式,形如:
  func1() = zsq(func1)
  如果我们在func1()调用之前,改变func1指向的的位置为zsq的位置,在处理好zsq接受的func1的参数问题,那不就实现了不修改源代码中func1()的写法,也实现了功能的改变了吗?
  如这个样子func1= zsq(func1)
  func1()
  有什么感觉了吗?这是不是相当于,调用了一个接收参数的函数(zsq(func1)),这个函数有一个返回值,并且把返回值赋值给了func1。
  那么关键来了,现在的核心问题就是:
  1、zsq这个函数,必须有一个返回值,这个返回值是一个函数。
  2、zsq这个函数,必须接把接受到的参数保存下来,留给下一个函数调用的时候来是使用。什么函数能在执行完毕后保存住一个传入的变量?什么函数的返回值是一个函数??
  闭包!!!!!!!!!!!!!!!!!!!!!!!!

#首先确定第一条:
def zsq():
    pass
    pass
    pass
    return function
#参考原函数:
def zsq(func):
    print("gong neng 1 diao yong")
    func()
#拟改写成:
def zsq(func):
    print("gong neng 1 diao yong")
    func()
    return function


#根据以下代码,确定第二条:参数要保存下来,那就要函数内定义个一个函数,在内函数中显式写出func,形成闭包
def zsq(func):
    def  XXXX():
        print("gong neng 1 diao yong")
        func()
    return function

  ↑这个内函数应该叫什么名字呢?我们知道,zsq(func)是要返回一个函数给func1的,然后使用func1()来调用一个函数,而且这个函数必须要包含原来func1()的功能,是func1()的一个超集,那满足这个功能的,不正是XXXX()函数么?既然我们之前写了return function,那么XXXX就正好改为function。

def zsq(func):
    def  function():
        print("gong neng 1 diao yong")
        func()
    return function

  ↓完整的定义和调用如下:

#完整调用
def zsq(func):
    def function():
        print("gong neng diao yong")
        func()
    return function

def func1():
    print("func1")

def func2():
    print("func2")
    
def func3():
    print("func4")

def func4():
    print("func4")

func1=zsq(func1)
func1()
gong neng diao yong
func1

  好的,刚才我们耗费了大量的精力,完成了一种在不改变原有函数名称的情况下,改掉了函数调用内容的方法,不可谓不精巧,不可谓不奇妙。但是直到现在,之前的需求还是没有完全满足,因为到现在为止,这个方法还是需要显式的调用func1=zsq(func1)一次,才能完成预想的任务。那么有很多个funcN的时候,不是一样需要手动调用吗?
  ↓别急,python已经为我们做了隐藏细节的处理,刚才的代码,我们只要稍加改动,就能完成预想的效果:

def zsq(func):
    def function():
        print("gong neng diao yong")
        func()
    return function

@zsq
def func1():
    print("func1")

def func2():
    print("func2")
    
def func3():
    print("func4")

def func4():
    print("func4")


# func1=zsq(func1)
func1()
gong neng diao yong
func1

  ↑其中,函数名称上方的@XXX,就叫做装饰器

  • 2-2 装饰器—2个装饰器

  装饰顺序:最靠近函数的最先解析,最原理函数的最后解析。
  why?
  代码解析,自上而下。当解析到@zsq1时,python就要准备为下面的函数进行装饰了,但是下面是不是函数?不是!
  下面是另一个装饰器@zsq2,所以python只能跳过@zsq1,继续看@zsq2能不能执行装饰,即@zsq2下面紧跟的是函数还是其他的装饰器,这里是函数,所以就先解析@zsq2了。

@zsq1
@zsq2
def name():
    print("this is name function")
    return "name"

print(name())

  ↓以下假设调用的函数为func1 (),解析过程大致为:


  ↑1、首先解析@zsq,此时函数func1指向print("func1")


  ↑2、此时函数已经自动调用并跳转至zsq1函数内,python自动把func1的值赋给了funcY(这是装饰器的工作机制),因此funcY指向print("func1")

  ↑3、函数zsq1函数内只有一条能执行的语句return,执行完return则zsq1函数执行完毕。这相当于python进行的自动调用zsq1完毕了,那么就要返回并执行func1()

  ↑4、跳出zsq1函数后,@zsq1功能已完成,这里我们暂时把@zsq1盖住不看。跳出zsq1函数后,func1接收了zsq1函数的返回值(python解释器自动进行的操作),实际上func1的指向已经发生了改变,指向的是原zsq1内部函数的代码,如图所示(注意这里手误把“lalala gong neng2”错写成了“gong neng diaoyong 2”)。

  ↑5、当系统想要开始执行func1()的代码时,发现还有一个装饰器@zsq,则继续开始解析@zsq。python将自动调用zsq函数。同时把当前的func1的值传递给了funcX

  ↑6、这里马上要执行return语句返回。


  ↑7、zsq函数返回后,func1的指向再次改变为zsq函数的内部函数为↓

        print("gong neng diao yong1")
        funcX()

  由上所知,funcX指向

        print("lalalal gong neng2")
        funcY()

  由上所知,funcY指向

         print("func1")

  那么我们做一个等式的替换,把上述3段代码进行融合:

        print("gong neng diao yong1")
        funcX()
        print("lalalal gong neng2")
        funcY()
        print("func1")
        print("gong neng diao yong1")
        print("lalalal gong neng2")
        print("func1")
li@li-ThinkPad-T420s:~/Desktop/py$ cd /home/li/Desktop/py ; env PYTHONIOENCODING=UTF-8 PYTHONUNBUFFERED=1 /usr/bin/python3 /home/li/.vscode/extensions/ms-python.python-2019.8.30787/pythonFiles/ptvsd_launcher.py --default --client --host localhost --port 39427 /home/li/Desktop/py/ceshi=======.py 
gong neng diao yong1
lalalal gong neng2
func1
li@li-ThinkPad-T420s:~/Desktop/py$ 

  ↑正好和程序验证的执行顺序是一致的。

def zsq(funcX):
    def function():
        print("zsq work")
        print("gong neng diao yong1")
        funcX()
    return function

def zsq1(funcY):
    print("zsq1 work")
    def function():
        print("lalalal gong neng2")
        funcY()
    return function

@zsq
@zsq1
def func1():
    print("func1")


def func2():
    print("func2")
    
def func3():
    print("func4")

def func4():
    print("func4")

func1()
#保存完整代码一段



  ↓考虑只有返回值的情况

def zsq1(funX):
    def zsq1in():
        return "zsq1  " + funX()+"  zsq1" 
    return zsq1in


def zsq2(funY):
    def zsq2in():
        return "zsq2  " +funY()+"  zsq2"
    return zsq2in


@zsq1
@zsq2
def name():
    return "name"


print(name())
zsq1  zsq2  name  zsq2  zsq1



  ↓既有代码,又有返回值

def zsq1(funX):
    def zsq1in():
        print("this is zsq1in function")
        return "zsq1  " + funX()+"  zsq1" 
    return zsq1in

def zsq2(funY):
    def zsq2in():
        print("this is zsq2in function")
        return "zsq2  " +funY()+"  zsq2"
    return zsq2in

@zsq1
@zsq2
def name():
    print("this is name function")
    return "name"

print(name())
this is zsq1in function
this is zsq2in function
this is name function
zsq1  zsq2  name  zsq2  zsq1

  ↓三个装饰器:

def zsq1(funX):
    def zsq1in():
        print("this is zsq1in function")
        return "zsq1  " + funX()+"  zsq1" 
    return zsq1in

def zsq2(funY):
    def zsq2in():
        print("this is zsq2in function")
        return "zsq2  " +funY()+"  zsq2"
    return zsq2in


def zsq3(funZ):
    def zsq3in():
        print("this is zsq3in function")
        return "zsq3  " +funZ()+"  zsq3"
    return zsq3in

@zsq1
@zsq2
@zsq3
def name():
    print("this is name function")
    return "name"

print(name())
this is zsq1in function
this is zsq2in function
this is zsq3in function
this is name function
zsq1  zsq2  zsq3  name  zsq3  zsq2  zsq1
  • 2-3 装饰器—装饰器的执行时间

  ↓注意看以下代码,这里没有调用任何函数

def zsq1(funX):
    print("this is zsq1 function")
    def zsq1in():
        print("this is zsq1in function")
        return "zsq1  " + funX()+"  zsq1" 
    return zsq1in

def zsq2(funY):
    print("this is zsq2 function")
    def zsq2in():
        print("this is zsq2in function")
        return "zsq2  " +funY()+"  zsq2"
    return zsq2in


def zsq3(funZ):
    print("this is zsq3 function")
    def zsq3in():
        print("this is zsq3in function")
        return "zsq3  " +funZ()+"  zsq3"
    return zsq3in

@zsq1
@zsq2
@zsq3
def name():
    print("this is name function")
    return "name"


# print(name())
this is zsq3 function
this is zsq2 function
this is zsq1 function

  ↑由此可知,装饰在调用之前就已经开始了。

  • 2-4 装饰器—重点强调

  • 2-5 装饰器—对有参数、无参数函数进行装饰

  ↓对无参的函数装饰

def zsq(fp):
    print("this is zsq function")
    def zsqin():
        print("this is zsqinner function")
        fp()
    return zsqin

@zsq
def f1():
    print("i am f1")

f1()
this is zsq function
this is zsqinner function
i am f1

  ↓对有参的函数装饰

def zsq(fp):
    print("this is zsq function")
    def zsqin():
        print("this is zsqinner function")
        fp(123)
    return zsqin

@zsq
def f1(num):
    print("i am "+str(num))

f1()
this is zsq function
this is zsqinner function
i am 123

  这样做虽然程序上没有错误,但是你却改变了原本函数的功能,因为原来的函数要传递参数,但是你现在调用却不用参数。而且把参数的值的传递直接写死到了装饰器函数的内函数,也是没有多少使用意义的。
  所以,原函数有参数,装饰器函数的内函数也一定要有参数。

def zsq(fp):
    print("this is zsq function")
    def zsqin(num):
        print("this is zsqinner function")
        fp(num)
    return zsqin

@zsq
def f1(num):
    print("i am "+str(num))

f1(1)

  ↓加入变长参数的处理。

def zsq(fp):
    print("this is zsq function")
    def zsqin(*num):
        print("this is zsqinner function")
        fp(*num)
    return zsqin

@zsq
def f1(a,b,c):
    print("%d%d%d"%(a,b,c))
@zsq
def f2(a,b,c,d):
    print("%d%d%d%d"%(a,b,c,d))

f1(1,2,3)
f2(1,2,3,4)
this is zsq function
this is zsq function
this is zsqinner function
123
this is zsqinner function
1234
  • 2-6 装饰器—对带有返回值的函数进行装饰

def zsq(fp):
    print("this is zsq function")
    def zsqin():
        print("this is zsqinner function")
        ret =fp()
        return ret
    return zsqin

@zsq
def f1():
    return "hahaha"
 
print(f1())
this is zsq function
this is zsqinner function
hahaha
  • 2-7 装饰器—通用装饰器

def zsq(fp):
    print("this is zsq function")
    def zsqin(*args,**kwargs):
        print("this is zsqinner function")
        ret =fp(*args,**kwargs)
        return ret
    return zsqin

@zsq
def f1():
    return "hahaha"
 
@zsq
def f2(a,b):
    print(a)
    print(b)

print(f1())

f2(1,2)
  • 2-8 装饰器—带有参数的装饰器

  还要再定义一个函数把原来的闭包套起来。
  python发现@装饰器有参数,先调用闭包的外层参数。

def zsq_arg(zhe):
    print("this is zsq_arg function")
    def zsq(fp):
        print("this is zsq function")
        def zsqin(*args,**kwargs):
            print("this is zsqinner function")
            ret =fp(*args,**kwargs)
            return ret
        return zsqin
    return zsq

@zsq_arg("zheshisha")
def f1():
    return "hahaha"

print(f1())
this is zsq_arg function
this is zsq function
this is zsqinner function
hahaha

  ↑zsq_arg("zheshisha")相当于一次执行函数,这个函数有个返回值,你返回谁,python就@谁,我觉得这里可以在内部放多个装饰器,然后根据传入的参数来动态选择用哪一个装饰器。

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