python函数之闭包函数与无参装饰器

时间:2023-03-10 05:52:56
python函数之闭包函数与无参装饰器

一、global与nonlocal

python函数之闭包函数与无参装饰器python函数之闭包函数与无参装饰器
#global
x = 1 def f1():
global x # 声明此处是全部变量x
x = 2
print(x) f1() # 调用f1后,修改了全局变量x = 2
print(x) # 打印结果为2 # nonlocal
def f1():
x = 1
def f2():
nonlocal x # 此处声明使用外层函数的变量x
x = 2 # 将外层函数进行了修改
print(x) #
f2()
print(x) # 因为f1()下的x已经被修改,所以打印结果为2 f1()

二、闭包函数

1、什么是闭包函数?

①、根据名词解析:

闭:定义在函数内部的函数,特点是只能在函数内部使用;

包:该函数引用了一个名字,改名字来自函数外层,但又不是全局名称空间

# 闭包函数
def f1()
x = 1
def f2():
print(x)
f2()
# f2()就是一个闭包函数

②、闭包函数升级:能够在全局名称空间使用闭包函数名称

在闭包函数中,使用了函数嵌套,函数名称空间与定义域的知识,要想能够在全局定义域使用闭包函数,就需要使用函数对象的知识

函数可以如变量一般被引用、被当做参数,当然也能被用来赋值给变量,函数名实质上绑定了存放函数内存空间的内存地址,在不+()的情况下,我们得到的是内存地址,就可以赋值到全局名称空间

# 闭包函数
def f1()
x = 1
def f2():
print(x)
return f2 # 此处返回函数f2的内存地址,千万不要加括号 f = f1() # f ------>调用f1()----->返回f2内存地址
f() # 此时,f指向了f2的内存地址,并且是全局名称空间的名称

③为函数传参的两种方式

  • 直接以参数的形式传入
  • 通过闭包函数的形式包给函数
python函数之闭包函数与无参装饰器python函数之闭包函数与无参装饰器
# 给函数传参的两种方式
#方式一
def f1(x):
print(x) f1("ksdffbksd") #方式二
def f2():
x = 2
def f3():
print(x)
f2() f2()

三、装饰器

1、什么是装饰器

装饰器思想:在开放封闭原则下为被修饰对象添加新功能,所谓开放原则是对拓展新功能是开放的,封闭原则是指对源代码不进行修改,不改变源代码调用方式。

实际生产中,如果需要对以上线的程序添加新功能,但是为了保证程序运行的稳定性与安全性,会根据装饰器思想进行添加。

装饰器:指创建一个工具,在遵循以下原则前提下,为被装饰对象添加新功能

  • 原则1:不修改被装饰对象源代码
  • 原则2:不修改被装饰对象调用方式

2、装饰器详解

现有需求:为函数增加计时功能,该如何实现?

  • 方案一:修改源代码
# 方案一:修改源代码
def f1():
start = time.time()
print("hhhh")
end = time.time()
time.sleep(0.8)
print("run time is %s" %(end - start)) f1()
# 改变了源代码,没有改变调用方式
  • 方案二:在函数后增加代码
#方案二:在函数后增加代码
start = time.time()
f1()
end = time.time()
print("run time is %s" %(end - start))
#没有改变源代码,也没有改变调用方式,但是需要重复代码,并且比较复杂
  • 方案三:将后面的功能代码写成函数
# 方案三:将后面的功能代码写成函数
def f2():
start = time.time()
f1()
end = time.time()
print("run time is %s" % (end - start)) f2()
# 没有改变源代码,但是改变了调用方式
  • 方案四:使用闭包函数,可以实现不改变源代码,并且不改变调用方式
# 方案四使用闭包函数,可以实现不改变源代码,并且不改变调用方式
def outter():
def f2():
start = time.time()
f1()
end = time.time()
print("run time is %s" % (end - start))
return f2 # 我们需要返回封装好的函数内存地址,所以千万不要加() f = outter()
f()
#实现了添加功能,也没有改变源代码,但是调用方式改变了
  • 方案五:使用修改后的闭包函数
  • #使用修改后的闭包函数
    def outter():
    def f2():
    start = time.time()
    f1()
    end = time.time()
    print("run time is %s" % (end - start))
    return f2

    f1 = outter() # 可以在*域中,将f2的内存地址赋值给f1,此时,原函数调用没有改变
    f1()
    #注意,此处的f1不是原来的f1,而是经过“打包”后的f2,只是名字也叫f1
    # 我们还发现一个问题,这个闭包函数只可以对f1有用,但是我们需要一个能够通用的工具

  • 方案六:通用装饰器
python函数之闭包函数与无参装饰器python函数之闭包函数与无参装饰器
#通用装饰器
def outter(func):
def f2():
start = time.time()
func()
end = time.time()
print("run time is %s" % (end - start))
return f2 f1 = outter(f1)
f1()
# 进一步的,我们将函数名按照常用的规范改写
def outter(func):
def wrapper():
start = time.time()
func()
end = time.time()
print("run time is %s" % (end - start))
return wrapper f1 = outter(f1)
f1()
#在这里,f1()是没有参数的,如果f1是有参数的呢?
  • 方案七:有参函数的装饰器
python函数之闭包函数与无参装饰器python函数之闭包函数与无参装饰器
# 有参函数的装饰器
def print1(name):
print(name)
time.sleep(1) def outter(func):
def wrapper(name): # 注意这里和无参函数的区别,由于我们之前的操作就是将封装好的wrapper函数重新复制,
# 但是实质还是wrapper,所以我们要在这里添加一个参数,为函数体内的修饰对象func()传参
start = time.time()
func(name) # 将wrapper接受的参数放进我们所修饰的对象
end = time.time()
print("run time is %s" % (end - start))
return wrapper print1 = outter(print1)
print1("hadsbf")
#上面将装饰器写死了,只能对print1函数有效,如何做到对所有函数有效呢?
  • 方案八:有参函数装饰器改进,使用*与**
python函数之闭包函数与无参装饰器python函数之闭包函数与无参装饰器
# 有参函数装饰器改进,使用*与**
def print1(name):
print(name)
time.sleep(1) def outter(func):
def wrapper(*args,**kwargs): # 由于我们不知道函数有多少参数,所以要用到*args与**kwargs来接受可变数量的参数
start = time.time()
func(*args,**kwargs) # 这里将上面的实参放到函数内,代表将传递过来的参数打散,最后的效果就是原样传递
end = time.time()
print("run time is %s" % (end - start))
return wrapper print1 = outter(print1)
print1("hadsbf")
# 此时,装饰器无论对于有参函数,还是无参函数都同样适用,但是如果有返回值呢?
  • 方案九:有返回值函数的装饰器
python函数之闭包函数与无参装饰器python函数之闭包函数与无参装饰器
# 有返回值函数的装饰器
def print1(name):
print(name)
time.sleep(1)
return 111 def outter(func):
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs) # 我们需要一个变量接受函数的返回值
end = time.time()
print("run time is %s" % (end - start))
return res # 我们装饰器的核心是wrapper函数,只需要在wrapper函数中提供返回值即可
return wrapper print1 = outter(print1)
print1("hadsbf")
  • python语法糖:python官方提供的一种简洁定义装饰器的语法,使用@
python函数之闭包函数与无参装饰器python函数之闭包函数与无参装饰器
# python语法糖
def outter(func):
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs) # 我们需要一个变量接受函数的返回值
end = time.time()
print("run time is %s" % (end - start))
return res # 我们装饰器的核心是wrapper函数,只需要在wrapper函数中提供返回值即可
return wrapper @outter # @+装饰器名称,可以快速完成 pint1 = outter(print1)的函数赋值
def print1(name):
print(name)
time.sleep(1)
return 111
  • python装饰器模板
python函数之闭包函数与无参装饰器python函数之闭包函数与无参装饰器
# 装饰器标准模板
def outter(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper
# 可根据实际的需求对模板进行修改
  • 多层装饰器嵌套
python函数之闭包函数与无参装饰器python函数之闭包函数与无参装饰器
# 多层装饰器嵌套
def outter1(func1):
def wrapper1(*args,**kwargs):
print("11111")
res1 = func1(*args,**kwargs)
return res1
return wrapper1 def outter2(func2):
def wrapper2(*args,**kwargs):
print("22222")
res2 = func2(*args,**kwargs)
return res2
return wrapper2 def outter3(func3):
def wrapper3(*args,**kwargs):
print("333333")
res3 = func3(*args,**kwargs)
return res3
return wrapper3 @outter1
@outter2
@outter3
def print1():
print("444444")

python函数之闭包函数与无参装饰器

当出现多个装饰器时,代码越靠后,则越先封装,如图,首先使用outter3封装print1(),可以看做一个整体:f3,然后再使用outter2修饰f3,修饰后可以看做一个整体:f2,最后使用outter1修饰f2,看做整体f1,所以最先运行的是f1,也就是outter1,遇到func1,则跳转到其修饰的outter2,运行wrapper2,遇到func2,则跳转到outter3,运行wrapper3,遇到func3,则跳转到源代码print1,运行完毕后,返回wrapper3,运行ruturn,退出outter3,如此往后逐层退出,最后结果为:

python函数之闭包函数与无参装饰器