第五天
前面我们说过,在函数体中,如果对变量有赋值操作,则证明这个变量是一个局部变量,并且它只会从局部变量中去读取数据。这样设计可以避免我们在不知道的情况下,获取到全局变量的值,从而导致一些错误数据的出现。如果使用全局变量,使用 global 关键字。
闭包、装饰器
闭包
简单来说,闭包的概念就是当我们在函数内定义一个函数时,这个内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用时,我们称之为闭包。
闭包的特点
必须有一个嵌套函数(一个函数在另一个函数中);
这个嵌套函数必须引用在封闭函数中定义的变量;
封闭函数必须返回嵌套函数;
闭包无法修改外包函数的局部变量;
#function_outside是外部函数,msg是外函数的临时变量
def function_outside():
msg = 'Hello Python'
#function_inside是内函数
def function_inside():
#内函数中用到了外函数的临时变量
msg = 'nice day'
return msg
#外函数的返回值是内函数的引用
return function_inside
解读
在Python语言中,当父函数体内定义了嵌套函数后,父函数可以把定义的嵌套函数作为嵌套函数的引用返回给调用者。
函数名称也是一个变量,它存储了函数的内存地址。函数的内存地址既能赋值给函数名称,也可以通过函数名称赋值给其它变量,只不过其它变量存储的不是函数的直接内存地址,而是函数名称的内存地址。
def function_outside():
def function_inside(x):
return x*2+1
a = function_inside
b = function_inside
print(function_inside(5)) # 11
print(a(5)) # 11
print(b(5)) # 11
return function_inside
上面的代码把function_inside函数名称分别赋值给局部变量a和b,分别执行function_inside(5)、a(5)、b(5),其执行结果是相同的。由此可以证明,变量a和b与function_inside指向同一个函数。在这种情况下,我们说a和b是line函数的引用。
当父函数把父函数体内的嵌套函数名称返回给调用者时,实际上是把嵌套函数名称的内存地址返回给调用者,调用者将返回的函数名称内存地址赋值给接收变量,调用者通过接收的变量就可以执行接收变量所引用的函数。
def function_outside():
def function_inside(x):
return x*2+1
return function_inside
jieguo = function_outside()
print(jieguo(5)) # 11
def function_outside():
b = 15
def function_inside(x):
return x*2+b
return function_inside
b = 5
jieguo = function_outside()
print(jieguo(5))
那么上面的结果是多少呢?
注意:闭包是一个独立的运行环境,不受外部环境的影响和约束。
闭包修改外部变量
其实要修改也很简单,可以在内函数中用nonlocal关键字声明,表示这个变量不是内部函数的变量,需要向上一层变量空间找这个变量。
def function_outside():
msg = 'Hello python'
def function_inside():
nonlocal msg
msg = 'Hi python'
print('inner msg:',msg)
print('调用内置函数之前的msg',msg)
function_inside()
print('调用内置函数之后的msg',msg)
function_outside()
# 调用内置函数之前的msg Hello python
# inner msg: Hi python
# 调用内置函数之后的msg Hi python
使用闭包的过程中,一旦外函数被调用一次返回了内函数的引用,虽然每次调用内函数,是开启一个函数执行过后消亡,但是闭包变量实际上只有一份,每次开启内函数都在使用同一份闭包变量。
def function_outside(x):
def function_inside(y):
nonlocal x
x += y
return x
return function_inside
a = function_outside(10)
print(a(1)) # 11
print(a(3)) # 14
装饰器
简言之,python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。
下面我们来简单说下装饰器的前世今生:
原始函数:
import time
def function_01():
print("Hello")
time.sleep(1)
print("Python")
function_01()
一般而言,如果我们想拓展原来函数代码,最直接的办法就是侵入代码里面修改。
现在要求这个函数执行的总时间,我们在原来代码直接修改:
import time
def function_01():
startTime = time.time()
print("Hello")
time.sleep(1)
print("Python")
endTime = time.time()
print("执行时间为:",endTime-startTime)
function_01()
# Hello
# Python
# 执行时间为: 1.0031132698059082
但是,如果这个方法是我们的核心代码呢?我们不可以直接修改源码,可以试着封装方法进行解决。这就类似于Java中的面向切面编程。
import time
def function_log():
startTime = time.time()
function_01()
endTime = time.time()
print("执行时间为:", endTime - startTime)
#核心代码
def function_01():
print("Hello")
time.sleep(1)
print("Python")
function_log()
那么这个时候,可能又会出现问题了,加入说这些核心代码有很多,我们难道需要调用多次上述的方法吗?这就难受了,那么这时候我们就需要Python中的装饰器这门技术了。装饰器需要理解闭包的概念。
import time
def function_log(function_01):
def wrapper():
startTime = time.time()
function_01()
endTime = time.time()
print("执行时间为:", endTime - startTime)
return wrapper
#核心代码
@function_log
def function_01():
print("Hello")
time.sleep(1)
print("Python")
if __name__ == '__main__':
f = function_01
f()
这里的function_log函数就是最原始的装饰器,它的参数是一个函数,然后返回值也是一个函数。其中作为参数的这个函数func()就在返回函数wrapper()的内部执行。然后在函数func()前面加上@deco,func()函数就相当于被注入了计时功能,现在只要调用func(),它就已经变身为“新的功能更多”的函数了。
那么问题再次出现,如果拓展的函数有参数呢?
import time
def function_log(function_01):
def wrapper(*args,**kwargs):
startTime = time.time()
function_01(*args,**kwargs)
endTime = time.time()
print("执行时间为:", endTime - startTime)
return wrapper
#核心代码
@function_log
def function_01(a,b):
print("计算两个参数之和")
time.sleep(1)
print("两个参数之和是",(a+b))
@function_log
def function_02(a,b,c):
print("计算三个参数之和")
time.sleep(1)
print("三个参数之和是",(a+b+c))
if __name__ == '__main__':
# f1 = function_01
# f1(3,5)
function_01(3,5)
function_02(3,4,5)
# 计算两个参数之和
# 两个参数之和是 8
# 执行时间为: 1.0026426315307617
# 计算三个参数之和
# 三个参数之和是 12
# 执行时间为: 1.0016562938690186
再升级,我们可以给一个函数加入多个装饰器。
import time
#装饰器1
def function_oop01(function_01):
def wrapper(*args,**kwargs):
print("这是第一个装饰器oop01开始")
function_01(*args,**kwargs)
print("这是第一个装饰器oop01结束")
return wrapper
#装饰器2
def function_oop02(function_01):
def wrapper(*args, **kwargs):
print("这是第二个装饰器oop02开始")
function_01(*args, **kwargs)
print("这是第二个装饰器oop02结束")
return wrapper
#核心代码
@function_oop01
@function_oop02
def function_01(a,b):
print("计算两个参数之和")
time.sleep(1)
print("两个参数之和是",(a+b))
if __name__ == '__main__':
f = function_01
f(3,5)
# 这是第一个装饰器oop01开始
# 这是第二个装饰器oop02开始
# 计算两个参数之和
# 两个参数之和是 8
# 这是第二个装饰器oop02结束
# 这是第一个装饰器oop01结束
总结
闭包、装饰器体现了Python中函数是第一公民,函数是对象、是变量,可以作为参数、可以是返回值,非常的灵活与强大。需要多去实践。
网友评论