Python第二十六天 python装饰器

时间:2023-03-09 07:35:06
Python第二十六天 python装饰器

Python第二十六天 python装饰器

装饰器
Python 2.4 开始提供了装饰器( decorator ),装饰器作为修改函数的一种便捷方式,为工程师编写程序提供了便利性和灵活性
装饰器本质上就是一个函数,这个函数接受其他函数作为参数,并将其以一个新的修改后的函数进行替换。

装饰器的作用
1、注入参数。为函数提供默认参数,生成新的参数等
2、记录函数的行为。可以统计函数的调用次数,缓存函数的结果,计算函数调用耗费的时间
3、预处理与后处理
4、修改调用时的上下文

函数可以赋值给另外一个变量名
函数可以嵌套
函数对象可以作为另外一个函数的参数
装饰器仅仅是利用前面的这些Python 知识加上Python 语法实现的一种高级语法

函数对象
在Python 语言中, def 语句定义了一个函数对象,并将其赋值给函数名。
也就是说,函数名其实只是一个变量,这个变量引用了这个函数对象。
因此,我们可以将函数赋值给另外一个变量名,通过这个新的变量名调用函数。

def say_hi():
print ("HI")
hello = say_hi
hello()

嵌套函数
在Python 语言中, def 语句是一个实时执行的语句,当它运行的时候会创建一个新的函数对象,并将其赋值给一个变量名
这里所说的变量名就是函数的名称。因为def 是一个语句,因此,函数定义可以出现在其他语句之中。

def outer(x , y) :
def inner () :
return x + y
return inner
f = outer(l , 2)
print(f())

在这个例子中,我们定义了一个名为outer 的函数, 并在outer 函数内部定义了inner 函数
outer 函数以返回值的形式返回inner 函数,我们将返回值保存在变量f 中,f 引用的是outer 函数内部的inner 函数
所以,当我们调用函数f 时,实际是调用的inner 函数

回调函数
回调函数是指将函数作为参数传递给另外一个函数,并在另外一个函数中进行调用。
回调函数并不是Python 语言特有的,在各个编程语言中都存在。

def greeting(f):
f() def say_hi():
print ("HI") def say_hello():
print("HELLO") greeting(say_hi)
greeting(say_hello)

装饰器
在Python 的装饰器语法中,内层函数的参数是被装饰的函数参数,外层函数的参数是被装饰的函数。

def say_hi():
print("hi") def bread(f):
def wrapper(*args, **kwargs ):
print("begin call {0}".format(f.__name__))
f()
print("finish call {0}".format(f.__name__))
return wrapper say_hi_copy = bread(say_hi)
say_hi_copy()

上面这段代码的执行结果如下:
begin call say_hi
hi
finish call say_hi

 

改造为装饰器

@bread
def say_hi(username='someone'): #被装饰函数,被装饰函数的参数一定要用关键字参数,不能用位置参数,否则不能装入字典!!!!!!!
print("hi")
print(username) def bread(f): #装饰器函数
def wrapper(*args, **kwargs ):
print("begin")
f()
print("finish")
print(kwargs.get("username")) #读取被装饰函数的参数,被装饰函数的参数一定要用关键字参数,不能用位置参数,否则不能装入字典
return wrapper say_hi() 这段程序和前面的程序作用一模一样,产生的结果也相同。区别在于,前面的程序显示地调用了bread 函数来封装say_hi 函数
这段程序通过Python 的语法糖来封装say_hi函数。在Python 中, say_hi 函数定义语句前一行的"@bread"语句表示对该函数应用bread装饰器
其中,"@"是装饰器的语法,"bread"是装饰器的名称

获取正确函数属性
对于一个函数,我们可以通过name 属性得到函数的名字,通过doc 属性得到函数的帮助信息。
但是, 一个被装饰器装饰过的函数,默认情况下,我们通过doc 和name 获取属性时,得到的却是装饰器中嵌套函数的信息

标准库的functools 模块中的wraps 装饰器。wraps 装饰器的作用是,复制函数属性至被装饰的函数
使用functools.wraps 装饰器装饰wrapper 函数。通过这样简单的修改就可以获取到add 函数的正确属性

from __future__ import print_function
import time
import functools def benchmark(func):
@functools.wraps(func)
def wrapper(*args , **kwargs):
t = time.time()
res = func(*args , ** kwargs)
print(func.__name__, time.time() - t)
return res
return wrapper @benchmark
def add (a, b):
'''Calculate the sum of two numbers'''
return a + b print(add.__name__)
print(add.__doc__)

inspect 模块
inspect 模块提供了许多有用的函数来获取活跃对象的信息。其中, getcallargs 函数用来获取函数的参数信息
getcallargs 会返回一个字典,该字典保存了函数的所有参数,包括关键字参数和位置参数。
也就是说, getcallargs 能够根据函数的定义和传递给函数的参数,推测出哪一个值传递给函数的哪一个参数。
getcallargs 推测出这些信息以后,以一个字典的形式返回给我们所有的参数和取值。

import functools
import inspect
def check_is_admin(func):
@functools.wraps(func)
def wrapper(* args, * *kwargs):
func_args = inspect.getcallargs(func , *args , ** kwargs)
if func_args.get('username')! = 'admin' :
raise Exception("This user is not allowed to put/get elem")
return f( * args, * *kwargs )
return wrapper

给装饰器传递参数
在Python 的装饰器语法中,内层函数的参数是被装饰的函数参数,外层函数的参数是被装饰的函数。
那么,如果装饰器本身也有参数应该怎么办呢?在Python 的装饰器语法中, 如果装饰器本身也有参数,则需要再嵌套一层函数。
这也是为什么读者看到的Python装饰器有时候是两层嵌套, 有时候是三层嵌套的原因

下面是一个带参数的装饰器。在这个装饰器的实现中,最外层的函数是装饰器的名称。
这个装饰器的作用是将被装饰的函数执行多次。具体执行的次数由装饰器的参数指定

from __future__ import print_function

def times(length=1):
def bread(func):
def wrapper(* args ,* *kwargs):
for i in range(length):
func(*args, **kwargs) # func代表被装饰函数sandwich(),所以被装饰函数有什么参数这里就要写什么,例如Django里面每个view函数都有request参数,装饰view函数就要写成func(request, *args, **kwargs)
return wrapper
return bread @times(5) #5传入到length,执行5次sandwich
def sandwich(name):
print (name) sandwich('hello')

装饰器的先后顺序

例子中,never_cache就要先于login_required被调用。

decorators = [never_cache, login_required]

@method_decorator(decorators, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'

@method_decorator(never_cache, name='dispatch')
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'

使用method_decorator有时会导致TypeError异常,因为参数传递的原因。当然,一般人碰不到的,碰得到的人都不一般,都能自己查Django官方文档掌握问题原因。

参考

http://www.liujiangblog.com/blog/37/


def record_functime():
'''
函数执行时间统计装饰器
:return:
'''
def stopwatch(callback):
@functools.wraps(callback)
def wrapper(*args, **kwargs):
start = time.time()
body = callback(*args, **kwargs)
end = time.time()
timelog = 'function name:'+callback.__name__ +'; execute time:' +str(start) +'; elapsed time:'+str(end - start)
log_helper.info(timelog)
return body
return wrapper
return stopwatch