用于管理和增强 函数
和 类
行为的代码,提供一种在函数或者类定义中插入自动运行代码的机制。
特点:提供了更明确的语法,更高的代码可维护性和更好的一致性。
可以通过函数或者类来定义装饰器,可以实现装饰器函数、装饰器类,装饰器函数可以用于修饰函数+类方法,类装饰器也可以用于修饰函数+类方法,但是类作为装饰器,来装饰类会遇到问题,建议使用函数来定义装饰器。
直接输入函数的名称,返回的是一个地址。可以将函数名赋值给变量,通过变量来执行函数。
函数同样也可以作为参数进行传递,下面的例子表明:可以通过传递函数名来确认要执行的函数,对于没有定义的函数动作,还可以通过lambda进行函数参数的返回。
# 返回的是被嵌套函数的执行结果 def func1(): def func2(): return "Hello" return func2() r = func1() print(r) # 返回的是被嵌套函数的 函数对象 def func3(): def func4(): return "Hello" return func4 r = func3() print(r)
运行的结果:
Hello <function func3.<locals>.func4 at 0x7f97d9c045e0>
返回的是函数对象,如果要返回函数的执行结果,可以直接运行得到。
在上面的基础之上,加上代码:
r = func3()() print(r)
运行结果:Hello
函数嵌套,返回值,和函数嵌套中定义返回的内容有关。
LEGB 从内到外,不使用本地的x,打算使用封装的x,使用nonlocal来定义x,意思就是这个变量的值不是本地的变量值,使用和封装的x的变量,如下是从内到外的一个作用域。
L : local E : enclosure G: global B: builtin
同样如果声明变量的值为global 的话,就会使用全局的变量的值。
def func(): x = 10 def func2(): x = 20 return x+10 return func2() def func3(): x = 10 def func4(): nonlocal x return x+10 return func4() print(func()) print(func3()) 输出: 30 20
例子2:
x = 500 def f1(): x = 100 def f2(): # 使用封装的x,会改变f1()中的x,global的x不会改变 nonlocal x print(f'Nonlocal的x:{x}') x += 20 print(f'函数f2的x:{x}') def f3(): # 使用全局的x,改变global的x的值 global x x += 20 print(f'函数f3的global的x的值是{x}') f2() print(f'函数f1外侧的x:{x}') f3() f1() print(f'Global x is:{x}') 输出: Nonlocal的x:100 函数f2的x:120 函数f1外侧的x:120 函数f3的global的x的值是520 Global x is:520
装饰器用于接收一个函数名的变量作为参数,可以对函数进行装饰:对结果进行处理、添加额外的功能等等操作,装饰器可以批量丰富函数的功能。
带参数的函数的装饰器,装饰器的参数是函数名称,装饰器函数是如何获取到函数的参数呢。
是通过;*args, **kwargs 来获取到的函数的参数。
以下的例子为:带参数 + 不带参数的装饰器的用法,可以看到带参数的函数say_hello('Lucy')可以正常传递。
import time def wrapperfun(func): def warpper(*args, **kwargs): print(func(*args, **kwargs) + '@' + str(time.time())) return warpper @wrapperfun def say_hello(name): return f'Hello {name}' @wrapperfun def say_hi(): return 'Hi' if __name__ == "__main__": say_hello("Lucy") say_hi() 输出: Hello Lucy@1658300817.562695 Hi@1658300817.562695
(*args,**kwargs)
来传递,表示传递所有的参数可以使用定义的函数装饰器来修饰类方法,使用的方法和函数基本一致
import time def wrapperfun(func): def warpper(*args, **kwargs): print(func(*args, **kwargs) + '@' + str(time.time())) return warpper class Student: def __init__(self,name): self.name = name # 定义的函数装饰器来修饰类方法 @wrapperfun def get_name(self): return self.name if __name__ == "__main__": s = Student('Lily') s.get_name() 输出结果: Lily@1658363063.2143705
例子02:
def wrapper_fun(func): def wapperf(*args,**kwargs): return func(*args,**kwargs).upper() return wapperf class Person: def __init__(self,name,age): self.name = name self.age = age # 用于修饰类方法 @wrapper_fun def get_info(self): return f'Person info: name-{self.name},age-{self.age}' if __name__ == '__main__': p = Person('Mike',33) print(p.get_info()) 输出结果: PERSON INFO: NAME-MIKE,AGE-33
定义一个类,做为装饰器,用来装饰一个函数。
使用了__call__方法,该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。
class AddTag: def __init__(self,func): self.func = func def __call__(self,*args,**kwargs): return '<p>'+self.func(*args,**kwargs)+'</p>' @AddTag def greeting(name): return f'Hi {name}' if __name__ == "__main__": print(greeting('Amanda')) 输出: <p>Hi Amanda</p>
例子2:
class Decorator: def __init__(self,func): self.func = func def __call__(self,*args,**kwargs): return self.func(*args,**kwargs).upper() @Decorator def greeting(name): return f'Hello,{name}' if __name__ == '__main__': print(greeting('Jim')) 输出: HELLO,JIM
1、没有使用装饰器
class AddTag: def __init__(self,func): self.func = func def __call__(self,*args,**kwargs): return '<p>'+self.func(*args,**kwargs)+'</p>' class Student: def __init__(self,name): self.name = name def get_name(self): print(self.name) if __name__ == "__main__": s = Student('Lucy') s.get_name()
返回的结果:Lucy
2、使用了类装饰器
类装饰中使用了__call__方法调用传递过来的函数对象
class AddTag: def __init__(self,func): self.func = func def __call__(self,*args,**kwargs): return '<p>'+self.func(*args,**kwargs)+'</p>' class Student: def __init__(self,name): self.name = name @AddTag def get_name(self): print(self.name) if __name__ == "__main__": s = Student('Lucy') s.get_name()
运行的结果:
抛出异常,提示参数的个数不正确,AddTag和get_name中的self字段冲突了
出现该问题的原因:AddTag装饰器类中有self这个参数,同样被修改的函数也有self这个参数,两个相同的参数导致无法区分。
例子:打印函数的耗时
import time def clock(func): def wrapper(*args, **kwargs): print(f'Start time:{time.ctime()}') func(*args, **kwargs) print(f'End time:{time.ctime()}') return wrapper @clock def intersect(sq1, sq2): res = [] for sq in sq1: if sq in sq2 and sq not in res: res.append(sq) else: print('Task done') print(f'res is:{res}') if __name__ == "__main__": sq1 = [1, 2, 4, 5, 421, 3, 14, 546, 4, 1, 1, 4, 1, 3, 444, 4, 5, 4] sq2 = [3.1, 3, 11, 32, 54, 1, 3, 544, 4, 4, 1, 55, 1, 1, 4145546, 14] intersect(sq1, sq2) # seq1 = set(sq1) # seq2 = set(sq2) # sq = seq1&seq2 # print(f'sq is:{sq}')
运行的结果:
定义一个装饰器,尽量使用函数的方式进行定义,函数的方式是通用的。
而通过类定义的装饰器,只能用来装饰函数,不经过其他处理无法装饰类方法。
结论:定义装饰器,尽量使用函数的方式来定义
在没有使用参数装饰器之前,添加装饰器后,起到修饰作用的参数是固定的,如函数中的
字符,如果想要修饰的参数可变,需要参数化装饰器。
没有使用参数化的装饰器
def add_tag(func): def wrapper(*args,**kwargs): return f'<p>{func(*args,**kwargs)}</p>' return wrapper @add_tag def say_hi(name): return f'Hi {name}' if __name__ == "__main__": print(say_hi('Lucy'))
使用参数化的装饰器
可以在之前没有参数化的装饰器上再定义一层函数,然后再加上返回值。
def tag(tag): def add_tag(func): def wrapper(*args,**kwargs): return f'<{tag}>{func(*args,**kwargs)}<{tag}>' return wrapper return add_tag @tag('body') @tag('p') @tag('div') def say_hi(name): return f'Hi {name}' if __name__ == "__main__": print(say_hi('Lucy'))
运行的结果:
<body><p><div>Hi Lucy<div><p><body>例子02 带参数的装饰器
01 - 不带参数
def decorate(func): def wrapper(*args,**kwargs): return '<p>' + func(*args,**kwargs) + '</p>' return wrapper @decorate def greeting(name): return f'Hello ,{name}' if __name__ == '__main__': print(greeting('Tom')) 输出: <p>Hello ,Tom</p>
02 - 带参数
如果装饰器带参数,就要在之前的装饰器上再封装一层即可,并逐层进行return
先写好一个不带参数的装饰器decorate,然后再加上一层函数tags并传递一个参数,tags函数返回decorate
并且一个函数,还可以被多个装饰器装饰,执行的顺序是从内到外的顺序,如下代码所示:先使用tags('p')进行装饰,然后再使用tags('div')进行装饰。
def tags(tag): def decorate(func): def wrapper(*args,**kwargs): return f'<{tag}>{func(*args,**kwargs)}</{tag}>' return wrapper return decorate @tags('div') @tags('p') def greeting(name): return f'Hello ,{name}' if __name__ == '__main__': print(greeting('Tom'))