Python:语法糖-Python可变参数与装饰器笔记

Python:语法糖-Python可变参数与装饰器笔记

九月 27, 2019
当前设备屏幕尺寸过小,推荐使用PC模式浏览。

了解装饰器先要了解Python的语法糖——

Python语法糖——可变参数

可变参数其实很好理解,参考以下代码:

1
2
3
4
def func(value, *args, **kwargs):
print(value)
print(args)
print(kwargs)
1
2
func(2, 3, 4, a=8, b=7) # 打印: 2 \n (3, 4) \n {'a': 8, 'b': 7}
# func(2, 3, 4, a=8, value=7) # 不合法,value字段已被占用

一些要注意的点:

  • *args传递的是一个tuple,例如上文把2 3 4 打包成一个tuple赋给args
  • **kwargs传递的是一个字典,例如上文的a=8b=7
  • *arsg要在**kwargs前,**kwargs总是在最后
  • 各参数的位置顺序为:func(位置参数, 默认参数, 可变参数, 命名关键字参数, 关键字参数),如下面这段代码:

注意位置顺序——

1
2
3
4
5
6
def func2(param, default_param=7, *arg, key_param1, **kwargs):
print(param)
print(default_param)
print(arg)
print(key_param1)
print(kwargs)

​ 调用这个函数:

1
func2(101, 202, 303, 404, key_param1=10, a=11, b=12)

装饰器模式

考虑一种场景,函数print_name(id)将会根据身份证号查找并打印某个人的姓名,其实现逻辑为:

1
2
3
4
5
6
7
8
9
10
11
first_name = {
'101': 'David',
'202': 'Tony',
'303': 'Shenpibaipao'
}

def find_first_name(id):
return first_name[id]

def print_name(id):
print(find_first_name(id))

那么随着业务修改,还需要输出用户的last_name该怎么办呢?或许我们可以修改print_name

1
2
3
4
5
6
7
8
9
10
11
last_name = {
'101': 'Coco',
'202': 'Lov',
'303': 'CNDZ'
}

find_last_name(id):
return last_name[id] # 添加新的业务逻辑

def print_name(id): # 修改原有业务逻辑
print(find_first_name(id), find_last_name(id))

但是OP设计模式告诉我们,“添”永远好过于“改”,那么我们或许能用一种更高明的方法,不改变print_name函数同时实现这个逻辑:

1
2
3
4
5
def wrapped_print_name(func,id):  # 接收一个函数func作为参数
func(id)
print(find_last_name(id)) # 添加逻辑

wrapped_print_name(print_name,'101') # 输出 David Coco,完成目标逻辑

每次使用都还要传一个函数,难免有点麻烦;同时,注意到上文传递函数时,还需要根据条件赋参,实际上改变了print_name的调用形式。所以,我们可以利用装饰器模式来解决这个问题。Python给了@这个装饰器语法糖让我们更好地完成这个任务,我们再次实现这个逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
def decorator(func):
def wrapper(*args, **kwargs): # 包裹器包裹住新的业务逻辑
func(*args, **kwargs)
print(find_last_name(args[0]))
return wrapper # 返回包裹器

@decorator
def print_name(id):
print(find_first_name(id))

print_name('101') # 输出 David Coco,完成目标逻辑同时没有改变其调用方式
# decorator(print_name)('101')# 或者不适用@语法糖,可以这么调用

这种方式可能看起来很怪,但确实是一种非常简洁明了的写法。但此时又有一种问题,我如果像进一步向包裹器内附加参数怎么办?比如我想先输出一行提示文字,再输出用户的姓名:

1
2
3
4
5
6
7
8
def print_decorator(param):
def decorator(func):
def wrapper(*args, **kwargs): # 包裹器包裹住新的业务逻辑
print(param) # 添加此行新业务逻辑
func(*args, **kwargs)
print(find_last_name(args[0]))
return wrapper # 返回包裹器
return decorator
1
2
3
@print_decorator(param='Find Name:')
def print_name(id):
print(find_first_name(id))

调用这个函数:

1
print_name('101') # 输出 Find Name: David Coco

Python装饰器的三层结构

  1. 第一层(装饰器包裹器):接受装饰器的新参数,返回装饰器
  2. 第二层(装饰器):接受被包裹的函数,返回内部包裹器
  3. 第三层(内部包裹器):接受参数,实现新业务逻辑

下面给出另外一个例子:

1
2
3
4
5
6
7
8
def deco(param):
def wrapper(func):
def inner_wrapper(*arg, **kwargs):
print(param, arg, kwargs)
inner_res = func(*arg, **kwargs)
return inner_res
return inner_wrapper
return wrapper
1
2
3
@deco(param="do what you want to do")
def origin(times, add, sub, num=1):
return times*(num + add) - sub
1
2
result = origin(1, 1, 0, num=5)
print("result =", result) # 打印 do what you want to do (1, 1, 0) {'num': 5}