python 装饰器
装饰器本质上就是一个函数,这个函数接受其他函数作为参数,并将其以一个新的修改后的函数进行替换。
Python中函数可以赋值给另外一个变量名,函数可以嵌套,以及函数对象可以作为另外一个函数的参数等。装饰器是利用这些python知识加上python语法实现的一种高级语法。
函数对象
可以将函数赋值给另外一个变量名,通过这个新的变量名调用函数。
def say_hi():
print("Hi")
hello = say_hi
hello()
函数嵌套
在Python语言中,def语句是一个实时执行的语句,当它运行的时候会创建一个新的函数对象,并将其赋值给一个变量名。这里所说的变量名就是函数的名称。
定义了一个名为outer的函数,并在outer函数内部定义了inner函数。outer函数以返回值的形式返回inner函数。将返回值保存在变量f中,f引用的是outer函数内部的inner函数。所以当调用函数f时,实际时调用的inner函数
def outer(x,y):
def inner():
return x + y
return inner
f = outer(1,2)
print(f())
装饰器原型
回调函数是指将函数作为参数传递给另外一个函数并在另外一个函数中进行调用。
回调函数例子如下:
定义了三个函数,分别是greeting、say_hi和say_hello。say_hi和say_hello这两个参数作为一个普通的参数传递给greeting函数。greeting函数通过函数参数获得了say_hi函数和say_hello函数的引用。因此,在greeting函数中调用f函数时,实际调用的时作为参数的say_hi函数和say_hello函数
def greeting(f):
f()
def say_hi():
print("hi")
def say_hello():
print("Hello")
greeting(say_hi)
greeting(say_hello)
更复杂的示例
定义了一个名为say_hi的函数和一个名为bread的函数,bread函数接收say_hi函数作为参数。在bread函数中,定义了一个名为wrapper的嵌套函数,在嵌套函数中,首先打印begin消息,然后再调用作为函数参数传递进来的say_hi函数,在say_hi函数调用完成以后,再打印end消息。最后,将嵌套函数作为返回值,返回给了bread函数的调用者say_hi_copy,say_hi_copy得到返回值后,执行函数调用
def say_hi():
print("hi")
def bread(f):
def wrapper(*args,**kwargs):
print(f"begin call {f.__name__}")
f()
print(f"finish call {f.__name__}")
return wrapper
say_hi_copy = bread(say_hi)
say_hi_copy()
#输出如下所示:
begin call say_hi
hi
finish call say_hi
在这段程序中,bread函数接收say_hi函数作为参数,并将其以一个新的修改后的函数进行替换。bread本身就是一个合法的装饰器,只需要使用Python的语法糖改造前面的程序,就完成了一个装饰器的定义和使用的完整例子。
def bread(f):
def wrapper(*args,**kwargs):
print(f"begin call {f.__name__}")
f()
print(f"finish call {f.__name__}")
return wrapper
@bread
def say_hi():
print("hi")
say_hi()
#输出如下所示
begin call say_hi
hi
finish call say_hi
上面这段程序和前面的程序作用一模一样,产生的结果也相同。区别在于,前面的程序显示地调用了bread函数来封装say_hi函数,这段程序通过Python语法糖来封装say_hi函数。@bread语句表示对该函数应用bread装饰器,@是装饰器的语法,bread是装饰器的名称。
装饰器完整示例
有一个特殊的栈,不但实现了先进先出的数据结构,还会检查操作栈的用户是否具有相应的权限,只有管理员才能够进行栈操作。
def check_is_admin(username):
if username != 'admin':
raise Exception("This user is not allowed to put/get elem")
class Stack(object):
def __init__(self):
self.storage = []
def put(self,username,elem):
check_is_admin(username=username)
self.storage.append(elem)
def get(self,username):
check_is_admin(username=username)
if not self.storage:
raise Exception("There is not elem in stack")
return self.storage.pop()
上面这段程序使用装饰器后效果会更好,使用装饰器后将清楚明了的看到添加元素和获取元素的逻辑。
def check_is_admin(f):
def wrapper(*args,**kwargs):
if kwargs.get('username') != 'admin':
raise Exception("This user is not allowed to put/get elem")
return f(*args,**kwargs)
return wrapper
class Stack(object):
def __init__(self):
self.storage = []
@check_is_admin
def put(self,username,elem):
self.storage.append(elem)
@check_is_admin
def get(self,username):
if not self.storage:
raise Exception("There is not elem in stack")
return self.storage.pop()
装饰器作为一种修改函数的方式,灵活应用,可以实现很多有意思的功能。
1)注入参数。为函数提供默认参数,生成新的参数。
2)记录函数的行为。可以统计函数的调用次数,缓存函数的结果,计算函数调用耗费的时间。
3)预处理与后处理
4)修改调用时的上下文。
示例:下面的sum_time函数就是一个统计函数运行时间的装饰器。
import time
def sum_time(f):
def wrapper(*args,**kwargs):
start_time = time.time()
res = f(*args,**kwargs)
over_time = time.time()
print(f.__name__,over_time-start_time)
return res
return wrapper
@sum_time
def add(a,b):
time.sleep(10)
return a+b
print(add(1,2))
使用装饰器以后函数属性的变化
装饰器接受一个函数作为参数,并将其以一个新的修改后的函数进行替换,获取一个函数被装饰器修改过的函数的属性将不能获取到正确属性信息。
@sum_time
def add(a,b):
time.sleep(10)
return a+b
def mul(a,b):
return a*b
print(mul.__name__)
#输出如下所示
#mul
print(add.__name__)
#输出如下所示
#wrapper
在没有使用装饰器的函数中可以正确获取函数的名字和帮助信息,而使用了sum_time装饰器装饰的函数将无法获取函数的属性。这种情况可以理解为,获取到的函数属性,实际上是装饰器返回给我们的这个函数的属性。
可以通过使用标准库的functools模块中的wraps装饰器即可。wraps装饰器的作用是,复制函数属性至被装饰的函数。
import time
import functools
def sum_time(f):
@functools.wraps(f)
def wrapper(*args,**kwargs):
start_time = time.time()
res = f(*args,**kwargs)
over_time = time.time()
print(f.__name__,over_time-start_time)
return res
return wrapper
@sum_time
def add(a,b):
time.sleep(10)
return a+b
def mul(a,b):
return a*b
print(mul.__name__)
#输出如下所示
#mul
print(add.__name__)
#输出如下所示
#add
增加了两行代码,分别是导入functools模块,以及使用functools.wraps装饰器装饰wrapper函数
使用inspect获取函数参数
Python标准库的inspect模块提供了许多用于获取活跃对象的信息,其中,getcallargs函数用来获取函数的参数信息。getcallargs会返回一个字典,该字典保存了函数的所有参数,包括关键字参数和位置参数.
import functools
import inspect
def check_is_admin(f):
@functools.wraps(f)
def wrapper(*args,**kwargs):
f_args = inspect.getcallargs(f,*args,**kwargs)
if f_args.get('username') != 'admin':
raise Exception("This user is not allowed to put/get elem")
return f(*args,**kwargs)
return wrapper
通过inspect.getcallargs方式,无论用户使用的是位置参数还是关键字参数都能进行正确处理。
给装饰器传递参数
再Python的装饰器语法中,如果装饰器本身也有参数,则需要再嵌套一层函数。
示例:下面是一个带参数的装饰器。最外层的函数时装饰器的名称。这个装饰器的作用是将被装饰的函数执行多次。具体执行的次数由装饰器的参数指定。
def times(length=1):
def bread(func):
def wrapper(*args,**kwargs):
for i in range(length):
func(*args,**kwargs)
return wrapper
return bread
@times(5)
def a(name):
print(name)
a("i love apple")