说到装饰器是我们每个学Python人中的心痛。
1.闭包
学习装饰器首先我们先学习闭包:
闭包条件
1 在一个外函数中定义了一个内函数。
2 内函数里使用了外函数的临时变量。
3 并且外函数的返回值是内函数的引用(即函数名)。
闭包的定义
闭包的概念就是当我们在函数内定义一个函数时,这个内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用时,我们称之为闭包。
出现闭包的原因
-
数据封装和隐藏: 一个闭包允许内部函数访问其外部函数的局部变量,但外部函数无法直接访问内部函数的变量。这允许数据封装和隐藏,因为外部作用域无法直接操作内部函数的数据。这有助于防止数据被意外修改或滥用,并增加了数据的安全性。
-
保持状态: 闭包允许函数在多次调用之间保持状态。内部函数可以访问并修改外部函数的局部变量,使函数可以记住之前的操作或状态。这对于实现记忆化、创建迭代器、计数器和状态机等功能非常有用。
出现的背景
一般情况下,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
闭包的基本结构:
def func1(msg): #外函数 name="xiao" def inner(): #内函数 na = name # 使用外函数的变量 nb = msg print(inner.__closure__) print(inner.__closure__[0].cell_contents) print(inner.__closure__[1].cell_contents) return inner #返回内函数的引用 f=func1(123) f() #判断函数是不是闭包函数用__closure__方法 如果是闭包函数就是返回一个元组包含<cell, # 如果不是闭包函数就就返回none。这个cell包含了我们被引用的所有外部变量
结果:
(<cell at 0x000001EFD0610CA8: int object at 0x00000000699F7D20>, <cell at 0x000001EFD0610CD8: str object at 0x000001EFD04B4C38>) 123 xiao
闭包存在的意义在哪里?
我认为闭包存在的意义在于它保存了外层函数的变量,如果他不能保存外存函数的变量,那么闭包和其他函数没有什么区别。
例子计数器:
def counter(): count = 0 # 这是外部函数的局部变量 def inner(): nonlocal count # 声明count变量为非局部变量 count += 1 return count return inner # 返回内部函数 # 创建两个独立的计数器 counter1 = counter() counter2 = counter() print(counter1()) # 输出 1 print(counter1()) # 输出 2
print(counter2()) # 输出 1
结果:
1
2 1
2.装饰器
比较好的博客地址https://zhuanlan.zhihu.com/p/583043320
装饰器:装饰器是一个函数或一个类,它是用来给其他函数增加新功能的,作用:在不改变原函数的调用方式和内部代码为其他函数添加新功能。
装饰器的内层函数就是一个闭包,因为它使用了外部函数的参数func,
原则:1.不能改变被修饰函数的源代码。
2.不能修改被修饰函数的调用方式。
高阶函数
高阶函数:指能够接受一个或多个函数作为参数,并或(后边的条件不一定是必须得)能够返回一个函数作为结果的函数.
具有以下特征:
接受函数作为参数: 高阶函数可以将其他函数作为参数传递给它们。这使得可以将不同的行为通过函数参数进行自定义。这通常用于实现回调函数、事件处理程序等。
返回函数: 高阶函数可以返回一个函数作为其结果。这允许创建函数工厂,根据不同的参数生成不同的函数,从而实现更灵活的函数组合。
函数组合: 高阶函数可以将多个函数组合在一起,以创建更复杂的功能。这通常用于函数式编程中的组合和管道操作。
抽象操作: 高阶函数可以将通用的操作抽象出来,以便在不同上下文中重复使用。这有助于减少重复性代码并提高代码的可维护性。
常见的高阶函数包括 map、filter、reduce 等,
例子:
def double(func, x): return 2 * func(x) # 定义一个简单的函数 def add_one(x): return x + 1 result = double(add_one, 3) # 调用 double 函数,将 add_one 函数和参数 3 传递进去 print(result) # 输出 8,因为 double(add_one(3)) = 2 * (3 + 1) = 8
double函数就是一个高阶函数。
补充:
嵌套函数:在一个函数里定义新的一个函数。
装饰器=高阶函数+嵌套函数
装饰器的两大特性
特性一:能把被装饰的函数替换成其他函数.
特性二:装饰器在加载模块的时候立即执行.
registry = [] def register(func): print("runnning register(%s)" % func) registry.append(func) return func @register def f1(): print('runing f1()') @register def f2(): print('runing f2()') def f3(): print('runing f3()') def main(): print('running main()') print('registery->', registry) f1() f2() f3() if __name__ == '__main__': main()
在其他py里导入该模块时
runnning register(<function f1 at 0x10ccba840>)
runnning register(<function f2 at 0x10ccba8c8>)
上边的例子说明了加载该模块时,装饰器就已经运行,被装饰器装饰的函数只有在明确的调用的时候才运行.
装饰器例子
例子要求:1.写一个装饰器,计算两个程序运行耗时。
2.不改变源代码和调用方式。
例子:
1 # Author :ZHANG 2 #-*-coding:utf-8-*- 3 import time 4 def text1(): 5 time.sleep(3) 6 print("in the text1") 7 def text2(): 8 time.sleep(2) 9 print("in the text2")
方法:
1 # Author :ZHANG 2 #-*-coding:utf-8-*- 3 import time 4 def solu(func): 5 def wreppe(): 6 star_time=time.time() 7 func() 8 stop_time=time.time() 9 print("耗时为%s秒."%(stop_time-star_time)) 10 return wreppe 11 @solu #这里其实为:text1=solu(text1) 12 def text1(): 13 time.sleep(3) 14 print("in the text1") 15 @solu #同text1 16 def text2(): 17 time.sleep(2) 18 print("in the text2") 19 text1() #这里并不是真的text1(),其实是一个替代,solu(text1)() 20 text2() #同text1
结果:
1 in the text1 2 耗时为3.000171661376953秒. 3 in the text2 4 耗时为2.0001144409179688秒.
这里我们要特别注意这个@符号,其实可以用下面的代码来代替只不过我们用@比较简便罢了,但是我们要知道真正的意义
import time def solu(func): def wreppe(): star_time=time.time() func() stop_time=time.time() print("耗时为%s秒."%(stop_time-star_time)) return wreppe def text1(): time.sleep(3) print("in the text1") text1=solu(text1)#这里其实就是变量值替换了 text1() #这里并不是真的text1(),其实是一个替代,solu(text1)()
继续增加要求:text2加上多个参数,包括关键参数
方法:
1 # Author :ZHANG 2 #-*-coding:utf-8-*- 3 import time 4 def solu(func): 5 def wreppe(*args,**kwargs): 6 star_time=time.time() 7 func(*args,**kwargs) 8 stop_time=time.time() 9 print("耗时为%s秒."%(stop_time-star_time)) 10 return wreppe 11 @solu #这里其实为:text1=solu(text1)=wreppe 12 def text1(): 13 time.sleep(3) 14 print("in the text1") 15 @solu #同text1 16 def text2(*args,**kwargs): 17 time.sleep(2) 18 print("text2:",args,kwargs) 19 text1() #这里并不是真的text1(),其实是一个替代,solu(text1)() 20 text2(2,"we",a=1) #同text1
结果:
1 in the text1 2 耗时为3.000171661376953秒. 3 text2: (2, 'we') {'a': 1} 4 耗时为2.0001144409179688秒.
马上到高潮:
新例子要求为:
home函数return后面添加“from home”
1 #-*-coding:utf-8-*- 2 username,password="zhang",12345 3 def deco(func): 4 def wreppe(*args,**kwargs): 5 user=input("enter your name>>>").strip() 6 passwd=int(input("enter your password").strip())#密码输入这里我简化了许多,现在是为了突出另外一个主题 7 if user==username and passwd==password: 8 func(*args,**kwargs) 9 print("\033[32;1muser has passed authentication\033[0m") 10 else: 11 exit("\033[31;1minvalid username or password\033[0m") 12 return wreppe 13 def index(): 14 print("welcome to index page") 15 @deco 16 def home(): 17 print("welcome to home page") 18 home()
注意这个home函数添加“”from home”后,如果你打印他会得到如下结果:
enter your name>>>zhang
enter your password12345
welcome to home page
user has passed authentication
None #注意
Process finished with exit code 0
结果return后面得到一个none而不是“from home”,这就不符合装饰器不修改被修饰函数源代码的要求。
方法:
1 username,password="zhang",12345 2 def deco(func): 3 def wreppe(*args,**kwargs): 4 user=input("enter your name>>>").strip() 5 passwd=int(input("enter your password").strip())#密码输入这里我简化了许多,现在是为了突出另外一个主题 6 if user==username and passwd==password: 7 res=func(*args,**kwargs) #改变这个区域,把func(*args,**kwargs) 执行结果传给一个变量,就可以了 8 print("\033[32;1muser has passed authentication\033[0m") 9 return res #改变这个区域,这里就相当于把res中储存的结果打印出来了,因为最后是print(home()) 10 else: 11 exit("\033[31;1minvalid username or password\033[0m") 12 return wreppe 13 def index(): 14 print("welcome to index page") 15 @deco 16 def home(): 17 print("welcome to home page") 18 return "from home" 19 20 print(home())
结果:
1 enter your name>>>zhang 2 enter your password12345 3 welcome to home page 4 user has passed authentication 5 from home
还有一种情况:@deco(参数)需要学习
-------------------------------------------------------------------------------------------------第二次题目-----------------------------------------------------------------------
情况一:函数中带有参数
import time def func(func): def inner(*args): #因为func2中需要传递参数,所以这里要有*args tm=args #这里返回的是一个元组 start=time.time() func(*args) #这里的*args是引用的inner的参数,这里为什么是*args,而不是args,因为*args返回的是一个元组,因为A,B是分开的,所以需要把这个元组打撒 stop=time.time() print("程序运行时间为{}秒".format(stop-start)) return inner @func def func2(a,b): print("func2:%s"%(a+b)) func2(3,5) #这里的真实意思 func(func2)(3,5)
结果:
func2:8
程序运行时间为0.0秒
情况二: 函数中带有返回值
import time def func(func): def inner(*args): tm=args #这里返回的是一个元组 start=time.time() set=func(*args) stop=time.time() print("程序运行时间为{}秒".format(stop-start)) return set return inner
@func
def func2(a,b):
print("func2:%s"%(a+b))
return "数学真好"
print(func2(3,5))
结果:
func2:8
程序运行时间为0.0秒
数学真好
情况三装饰器中带参数。即有三层函数,通过传入不同的装饰器参数来控制装饰器的行为
给内容添加标签,函数控制内容,装饰器控制添加什么标签
from functools import wraps
def html_tags(tag_name): """ 给函数添加tag标签 :param tag_name: 标签名字 :return: """ def wrapper_func(func):
@wraps(func) def wrapper(*args,**kwargs): content =func(*args,**kwargs) return "<{tag}>{content}</{tag}>".format(tag=tag_name,content=content) return wrapper return wrapper_func @html_tags("b") #这里就等于 hello = html_tags("b)(hello) def hello(name='Toby'): """ 返回要贴标签的内容 :param name: :return: """ return 'hello{}!'.format(name) b = hello("小花") #这里就等于 html_tags('b')(hello)('小花') print(b)
结果:
<b>hello小花!</b>
装饰器的固定格式:
from functools import wraps
def wrapper(func):
@wraps(func) def inner(*args,**kwargs): re= func(*args,**kwargs) return re return inner
装饰器修复技术(wraps)
装饰器修复技术是用来修复执行函数称为真正的函数,作用将被修饰的函数的某些属性比如__doc__,__name__赋给装饰器中的闭包函数,作用它能保留被装饰函数的名称和docstring。如果你不需要这些属性,wraps就没有什么用。
先不加wraps的情况
def wrapper(func): def inner(*args,**kwargs): print("在前面执行") func() print('在后面执行') return inner @wrapper def f(): """ 这是一个用来测量 :return: """ print("hahaa") if __name__ == '__main__': print(f.__name__) print(f.__doc__)
结果为:
我们可以看到执行f()并不是执行真正的f函数,而是执行的wrapper(f)(),也就是真正执行的inner() 函数
inner
None
加wraps的情况
from functools import wraps def wrapper(func): @wraps(func) def inner(*args,**kwargs): print("在前面执行") func() print('在后面执行') return inner @wrapper def f(): """ 这是一个用来测量 :return: """ print("hahaa") if __name__ == '__main__': print(f.__name__) #修饰的函数名 print(f.__doc__) #修饰函数名里边的注释
结果:
f 这是一个用来测量 :return:
装饰器还具有缓存的功能
当装饰器的外层函数的变量是可变对象的时候,该变量具有计数器的作用,也可以当缓存
def decorator(func): """ 记录被装饰函数的执行次数 :param func: :return: """ #注意这里使用可变对象 a = [0] def decorated_func(*args,**keyargs): func(*args, **keyargs) #因为闭包是浅拷贝,如果是不可变对象,每次调用完成后符号都会被清空,导致错误 a[0] += 1 print ("%s have bing called %d times" % (func.__name__, a[0])) return decorated_func @decorator def func(x): print (x) func(3) func(3)
结果:
3 func have bing called 1 times 3 func have bing called 2 times
注意: 如果你把@decorator去掉话,这样调用
decorator(func)(3)
decorator(func)(3)
装饰器就没有了缓存的功能
结果:
3 func have bing called 1 times 3 func have bing called 1 times
具体原因,我也不清楚
装饰器的执行顺序
def one(func): print('----1----') def two(): print('----2----') func() return two def a(func): print('----a----') def b(): print('----b----') func() return b @one @a def demo(): print('----3----') demo()
结果:
----a---- ----1---- ----2---- ----b---- ----3----
我们这里可能大多数人容易猛,我们不用@我们用另外一种方式来写装饰器
def one(func): print('----1----') def two(): print('----2----') func() return two def a(func): print('----a----') def b(): print('----b----') func() return b def demo(): print('----3----') one(a(demo))()
结果:
----a---- ----1---- ----2---- ----b---- ----3----
我们可以发现这两种方法结果一样,但是第二种装饰器方法容易理解
我们只需要研究它的执行顺序就可以
one(a(demo))()
首先是demo这个参数传进来
第二步是执行a(demo) 结果打印__a__ 返回b函数名
第三步是执行one(b)函数,把b当做函数穿进去,打印__1__,返回two
第四步是执行two(),打印__2__,执行b函数,打印__b__,执行demo函数
总结执行顺序:
先调用@a执行a函数的外函数,--->one函数的外函数---->one内函数---->a函数的内函数---->执行demo函数
按照实现功能来说:
先实现one函数的功能,再实现a函数的功能
在实际应用的场景中,当我们采用上面的方式写了两个装饰方法比如先验证有没有登录 @login_required
, 再验证权限够不够时 @permision_allowed
时,我们采用下面的顺序来装饰函数:
@login_required @permision_allowed def f() # Do something return
类装饰器
类装饰器是Python中一种高级的装饰器形式,它允许你使用类来装饰函数或其他可调用对象。类装饰器通常定义一个类,该类包含 __init__
和 __call__
方法,其中 __init__
用于初始化装饰器的状态,而 __call__
方法用于实际装饰目标函数。这使得你可以在装饰器中存储状态,同时保持与函数装饰一样的灵活性。
class MyDecorator: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): # 在调用目标函数之前执行的操作 print("在函数调用前执行.") result = self.func(*args, **kwargs) # 在调用目标函数之后执行的操作 print("在函数调用后执行.") return result @MyDecorator # 相当于MyDecorator(my_function) def my_function(): print("函数被调用") my_function() #实际执行的是MyDecorator(my_function)()
结果:
在函数调用前执行.
函数被调用
在函数调用后执行.