装饰器
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。
举个简单的栗子:
# 定义一个记录日志用来调试的函数
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()
return wrapper
# 定义一个say_hello函数
def say_hello():
print("hello!")
# 调用debug函数并将say_hello函数当作参数传入,返回的是wrapper函数
hello = debug(say_hello)
# 执行wrapper函数
hello()
[DEBUG]: enter say_hello()
hello!
debug函数对say_hello函数进行了包装,也就是增加了更多功能,实际上,debug函数已经是一个装饰器了
更加简单的写法是使用语法糖语法@debug
,示例如下:
# 定义一个记录日志用来调试的函数
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()
return wrapper
@debug
# 定义一个say_hello函数
def say_hello():
print("hello!")
say_hello()
[DEBUG]: enter say_hello()
hello!
@debug
的效果与hello = debug(say_hello)
是一样的,是不是很惊艳!
关于参数问题
定装饰器函数wrapper中调用了原函数say_hello,但如果原函数有参数,或者有很多参数的时候,可以利用python多参数形式,如下:
# 定义一个记录日志用来调试的函数
def debug(func):
def wrapper(*args, **kwargs):
print("[DEBUG]: enter {}()".format(func.__name__))
return func(*args, **kwargs)
return wrapper
@debug
# 定义一个say_hello函数
def say_hello(name, world):
print(world, name)
say_hello('Jack', 'hello')
[DEBUG]: enter say_hello()
hello Jack
通过使用*args, **kwargs
这得传参数形式,支持元组形式或字典形式的传值结构,不管是有参数还是多个参数,都可以很好的应对
带参数的装饰器
定义一个可以指定日志输出级别的“日志输出装饰器函数”
def logging(level):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=level,
func=func.__name__))
return func(*args, **kwargs)
return inner_wrapper
return wrapper
@logging(level='INFO')
def say(something):
print("say {}!".format(something))
# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)
@logging(level='DEBUG')
def do(something):
print("do {}...".format(something))
say('hello')
do("my work")
[INFO]: enter function say()
say hello!
[DEBUG]: enter function do()
do my work...
解析:
当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG'),它其实是一个函数,会马上被执行
而被执行的logging函数返回的结果,才是一个装饰器函数O(∩_∩)O哈哈~
偏函数
偏函数可以设定函数的默认行为,示例如下:
num = int('12345')
print(num)
12345
在默认情况下,int()函数会将字符串数字转换成十进制的数据,便也可以通过设定参数来让int()函数将其转换成八进制数字,如下:
num = int('12345', base=8)
print(num)
5349
那如何改变int()函数的默认行为呢?
# 首先导入 functools 模块
import functools
# 定义一个默认转换为二进制的int函数
int2 = functools.partial(int, base=2)
# 调用
int2('10110101')
181
functools.partial的作用就是将固定一个函数的默认行为,从而简化之后的使用
来看看关于它的描述:
Init signature: functools.partial(self, /, *args, **kwargs)
Docstring:
partial(func, *args, **keywords) - new function with partial application
of the given arguments and keywords.
所以,这个方法可以接收函数、*args、**kwargs这些对象
这里的base=2是字典结构的参数
max2 = functools.partial(max, 10)
max2(5, 6, 7) # 相当于max2(10, 5, 6, 7)
10
小结
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
网友评论