什么是装饰器?
python装饰器(fuctional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能。
这个函数的特殊之处在于它的返回值也是一个函数,这个函数是内嵌“原“函数的函数。
1. 闭包函数
装饰器本身就是一个闭包函数,要理解装饰器首先可以来理解一下什么是闭包函数,简单地说闭包函数就是内部函数以及调用的外部环境组成的实体
。
python是一种面向对象的编程语言,在python中一切皆对象,这样就使得对象所拥有的属性,函数也同样拥有。这样我们就可以理解在函数内创建一个函数的行为是完全合法的。这种函数被叫做内嵌函数,这种函数只可以在外部函数的作用域内被正常调用,在外部函数的作用域之外调用会报错,例如:
而如果内部函数里引用了外部函数里定义的对象(甚至是外层之外,但不是全局变量),那么此时内部函数就被称为闭包函数。闭包函数所引用的外部定义的变量被叫做自由变量。闭包从语法上看非常简单,但是却有强大的作用。闭包可以将其自己的代码和作用域以及外部函数的作用结合在一起。下面给出一个简单的闭包的例子:
def count():
a = 1
b = 1
def sum():
c = 1
return a + c # a - 自由变量
return sum
总结:什么函数可以被称为闭包函数呢?主要是满足两点:函数内部定义的函数;引用了外部变量但非全局变量。
2. 函数装饰器
python装饰器本质上就是一个闭包函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象(函数的指针)。装饰器函数的外部函数传入我要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数。从上面这段描述中我们需要记住装饰器的几点属性,以便后面能更好的理解:
实质: 是一个函数
参数:是你要装饰的函数名(并非函数调用)
返回:是装饰完的函数名(也非函数调用)
作用:为已经存在的对象添加额外的功能
特点:不需要对对象做任何的代码上的变动
python装饰器有很多经典的应用场景,比如:插入日志、性能测试、事务处理、权限校验等。装饰器是解决这类问题的绝佳设计。并且从引入中的列子中我们也可以归纳出:装饰器最大的作用就是对于我们已经写好的程序,我们可以抽离出一些雷同的代码组建多个特定功能的装饰器,这样我们就可以针对不同的需求去使用特定的装饰器,这时因为源码去除了大量泛化的内容而使得源码具有更加清晰的逻辑。
2.1最简单的函数装饰器
参考一下代码:
def decorator(func):
def wrapper(*args, **kwargs):
print('hello world')
func(*args, **kwargs)
return wrapper
@decorator
def func():
print('hello python')
func()
上面的装饰器实现了最简单的功能,在每次调用func函数之前先输出hello world。
说明:由装饰器装饰过得函数虽然函数名称为改变,但是其实所执行的函数代码改变,上述装饰器装饰后func变为func = decorator(func)
由最简单的装饰器引申,使用装饰器实现斐波那契数列和爬楼梯问题。
斐波那契数列:斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368…
特别指出:第0项是0,第1项是第一个1。
这个数列从第3项开始,每一项都等于前两项之和。
def decorator(func):
dic = {}
def wrapper(*args, **kwargs):
'''
此处为错误写法
if args in dic:
return dic[args]
dic[args] = func(*args, **kwargs)
'''
if args not in dic:
dic[args] = func(*args, **kwargs)
return dic[args]
return wrapper
@decorator
def fibonacci(n):
if n <= 1:
return 1
return fibonacci(n-1)+fibonacci(n-2)
print(fibonacci(100))
我曾经犯过的一个错误,wrapper函数将return语句放在了if语句中,导致wrapper函数并不是每次都有返回值,出现了Nonetype类型无法相加的错误。斐波那契数列的递归解法是必须要有返回值的。
爬楼梯问题:题意:
有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?
def decorator(func):
dic = {}
def wrapper(*args, **kwargs):
if args not in dic:
dic[args] = func(*args, **kwargs)
return dic[args]
return wrapper
@decorator
def climb(n, steps=(1,2)):
count = 0
if n == 1:
count = 1
elif n > 1:
for step in steps:
count += climb(n-step, steps)
return count
climb(100)
可以看出,装饰器函数无需改变,只需要改变被装饰函数即可。而且装饰器并没有修改原来的代码,反而能快速提高代码运行效率。
2.2带参数的函数装饰器
函数装饰器灵活多变,为了满足需求,还可以给函数增加带参数的装饰器,带参数的装饰器的一个比较好的应用就是为函数增加类型检查。
参考下面代码
from inspect import signature
def typeassert(*ty_args, **ty_kwargs):
def decorator(func):
sig = signature(func)
#获得函数签名,并得到一个有序字典,这里是每个参数及其对应的类型
btypes = sig.bind_partial(*ty_args, **ty_kwargs).arguments
# print(btypes)
def wrapper(*args, **kwargs):
#获得变量和值,并判断变量的值是否符合类型规定
#print(sig.bind(*args, **kwargs).arguments)
for name, obj in sig.bind(*args, **kwargs).arguments.items():
if name in btypes:
if not isinstance(obj, btypes[name]):
raise TypeError('"%s" must be "%s" '%(name, btypes[name]))
return func(*args, **kwargs)
return wrapper
return decorator
@typeassert(str, int, int)
def func(a, b, c):
print(a,b,c)
func('1',2,3)
func(1,2,3)
执行func(1,2,3)会出现类型错误。
2.3参数可改的装饰器
在可带参数的装饰器的基础上进行修改传递参数的值。
实际案例:为分析程序内那些函数执行时间开销较大,可以定义一个带有timeout参数的函数装饰器,装饰器实现的功能为:
- 统计被装饰函数单次调用运行时间。
- 时间大于参数timeout的,将此次函数调用记录到log日志。
- 运行时可以修改timeout的值
import time
import logging
def warn(timeout):
def decorator(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
used = time.time()-start
if used > timeout:
msg = "'%s': %s > %s"%(func.__name__, used, timeout)
logging.warning(msg)
return res
def setTimeout(k):
#必须要写,否则在该函数中创建的的timeout不影响外部的timeout变量
nonlocal timeout # nonlocal在python3中出现的
timeout = k
#此处添加函数的函数属性
wrapper.setTimeout = setTimeout
return wrapper
return decorator
from random import randint
@warn(1.5)
def test():
print('In test')
while randint(0,1): # 生成0或者1
time.sleep(0.5)
for _ in range(30):
test()
需要对不同的函数进行测试,且不同的函数要记录不同的时间,则该装饰器的功能就体现出来了。
3. 类装饰器
前面我们提到的都是让 函数作为装饰器去装饰其他的函数或者方法,那么可不可以让 一个类发挥装饰器的作用呢?答案肯定是可以的,一切皆对象嚒,函数和类本质没有什么不一样。类的装饰器是什么样子的呢?
class Decorator(object):
def __init__(self, f):
self.f = f
def __call__(self):
print("decorator start")
self.f()
print("decorator end")
@Decorator
def func():
print("func")
func()
这里有注意的是:call()是一个特殊方法,它可将一个类实例变成一个可调用对象。类装饰器中同样可以实现带参数和参数可改的装饰器。
4.装饰器链
一个python函数也可以被多个装饰器修饰,要是有多个装饰器时,这些装饰器的执行顺序是怎么样的呢?
可见,多个装饰器的执行顺序:是从近到远依次执行。
5. python装饰器库
def decorator(func):
def inner_function():
pass
return inner_function
@decorator
def func():
pass
print(func.__name__)
# 输出: inner_function
上述代码最后执行的结果不是 func,而是 inner_function!这表示被装饰函数自身的信息丢失了!怎么才能避免这种问题的发生呢?
可以借助functools.wraps()函数:
from functools import wraps
def decorator(func):
@wraps(func)
def inner_function():
pass
return inner_function
@decorator
def func():
pass
print(func.__name__)
#输出: func
文中有我个人的理解也有摘抄的别人的,如有错误的地方欢迎下方评论告诉我,我及时更正,大家共同进步