章节号 | 内容 |
---|---|
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就@谁
,我觉得这里可以在内部放多个装饰器,然后根据传入的参数来动态选择用哪一个装饰器。