python基础三
- 2019 年 10 月 6 日
- 笔记
一、揭开函数名的真实面目
我们创建一个函数给的命名实际是代表一个内存地址,加上括号才会运行函数中的函数体,如果不加括号,可以自己试一下,输出的是一个内存地址,这样看来,他实际上他和一个变量差不多,只不过函数代表了一个功能,因此函数也可以作为参数传入函数,也可以作为返回值返回。
二、函数嵌套(函数中的函数)
def zhangsan():
def lisi():
print("我是lisi")
print("我是zhangsan")
lisi()
zhangsan()
当我们在zhangsan函数中没有调用lisi时,我是lisi是不会输出的。只有在调用的时候才会输出。以上只是2级嵌套,你可以像列表一样,多次嵌套。
注意:在嵌套函数中我们内部需要修改外部的变量时不能使用关键字global,
global影响的是函数外的全局变量。需要使用nonlocal(python3中),例如:
age =111
def zhangsan():
age = 18
def lisi():
nonlocal age
age +=1
print("lisi的年龄为",age)
print("zhangsan的年龄为",age)
lisi()
zhangsan()
print("age:",age)
我们运行一下会发现,age并没有影响到函数(zhangsan外)的age。如果换成global,就会影响到全局变量。
三、闭包
例如一个简单的闭包:
def zhangsan():
age = 18
def lisi():
print(age)
return lisi
闭包的概念:首先它是一个嵌套函数,其次在内部函数中引用了外部函数的变量,我们称这是一个闭包。
其次我们为什么要以返回值的形式返回lisi,我们可以想一下,如果我们只想用到lisi这个函数,如果我们用二中的方法,每次调用zhangsan,那么每次都要走一遍zhangsan,这显然不是我们想要的,因此,我们如果只想调用lisi可以这样写:wangwu = zhangsan()#此时wangwu代表的就是lisi的内存地址,但并没有去执行它。wangwu()#这样就实现调用lisi这个内部函数,并且可以引用到zhangsan中age这个变量。
四、装饰器
4.1普通装饰器
首先,思考一个问题,我们进入一个网页,再点击个人信息,会提醒需要登录才能查看,那么需要我们登录的页面很多,每次都调用很麻烦,所有就有了装饰器。
它需要遵循一个原则:开放封闭原则
开放:就是说要有利于扩展新的功能,比如更新的时候。
封闭:在扩展新功能的时候,不能修改原来的代码。
例如:
def login():
print("登录")
def func():
print("点击个人信息")
A写好登录,B写好个人信息,C想要调用个人信息他不会去想着调用登录,B需要将这两个功能结合起来,但是不能修改原来的功能,于是就有了:
def login(func):
print("登录")
def log():
func()
return log
def func():
print("点击个人信息")
func = login(func)
f()
用闭包的知识可以解决这个问题,func这个方法就叫做装饰器。但是一想要加上func = login(func)这样我们就调用的不是原来的函数名了,于是乎就有了:
def login(func):
print("登录")
def log():
func()
return log
@login
def func():
print("点击个人信息")
func()
利用在该函数上方添加一个@加函数名代替func = login(func)。我们称它为语法糖,嗯,真甜。
现在调用func就相当于调用login了,但是当func函数有返回值的时候,我们按照上述写法就获取不到了,因此修改成:
def login(func):
print("登录")
def log():
ret = func()
return ret
return log
@login
def func():
print("点击个人信息")
return "ok"
print(func())
总结:当我们执行到func()的时候,会跳到@login,@login相当于执行func = login(func),此时就执行login函数了。执行后的结果返回给func,此时func得到的值就是ok的内存地址,再回到func(),便会输出ok。
问题来了:
当我们新添加的功能,也就是func函数需要一个参数时候在装饰器函数中如何添加?下一个功能也可能需要这个装饰器需要二个参数该怎么办?也是就我们的参数是动态的,那就添加动态参数掰,将login函数修改成:
def login(func):
print("登录")
def log(*args,**kwargs):
ret = func(*args,**kwargs)
return ret
return log
此时无论添加几个都不会受到影响。
4.2带参数的装饰器
首先如果我们添加一个参数@login(True),当有参数时,我们将@和login(True)拆开看,login(True)得到返回值log函数的内存地址也就是@log,显然我们的func放在哪里呢?显然会报错,于是我们可以在外再嵌套一层变成:
def log_out(F):
def login(func):
print("登录")
def log(*args,**kwargs):
if F==True:
ret = func(*args,**kwargs)
return ret
else:
return "NO"
return log
return login
@log_out(True)
def func():
print("点击个人信息")
return "ok"
print(func())
这样再按照之前的理解方式就明白了,log_out(True)表示login也就是@login,
我们只是在原来的基础上添加了一个开关,也就是说当装饰器带参数时,就需要三层嵌套了。
4.3 一个函数使用多个装饰器
def zhangsan1(fun1):
def lisi1(*args,**kwargs):
print("执行被装饰函数前111111")
fun1(*args,**kwargs)
print("执行被装饰函数后111111")
return lisi1
def zhangsan2(fun2):
def lisi2(*args,**kwargs):
print("执行被装饰函数前222222")
fun2(*args,**kwargs)
print("执行被装饰函数后222222")
return lisi2
@zhangsan1
@zhangsan2 #zhangsan3 = zhangsan2(zhangsan3 )
def zhangsan3():
print("我是被装饰的函数333333")
zhangsan3()
执行的结果:
执行被装饰函数前111111
执行被装饰函数前222222
我是被装饰的函数333333
执行被装饰函数后222222
执行被装饰函数后111111
执行顺序:首先当调用zhangsan3的时候,先执行@zhangsan2,@zhangsan2等同于zhangsan3 = zhangsan2(zhangsan3)也就是返回了lisi2,然后执行@zhangsan1,@zhangsan1等同于zhangsan3 = zhangsan1(lisi2)注意,带入的函数变成了lisi2,而不是zhangsan3,也就返回lisi1,接下来再加上一个括号执行,也就是执行lisi1中的第一句。#执行被装饰函数前111111,接着fun1()也就是lisi2(),就会输出#执行被装饰函数前222222,回到zhangsan2后,带入的参数又变成了zhangsan3,所以输出了#我是被装饰的函数333333,接着输出执行被装饰函数后222222,然后再回到最初调用的地方输出#执行被装饰函数后111111。。。。
可能也不知道我再说撒,。。。。。。。你也可以暂时记住这个顺序,
你也可以使用pycharm打上断点,看一遍执行顺序就会明白。
写的快吐血,溜了。