函数定义的弊端
- Python 是动态语言, 变量随时可以被赋值且赋值为不同的类型
- Python不是静态编译型语言, 变凉了性是在运行期决定的
- 动态语言很灵活但这种特性也是弊端
def add(x, y):
return x + y
print(add(4, 5))
print(add('hello', 'world'))
# add(4, 'hello') 报错
运行结果
9
helloworld
- 难发现 :由于不作任何类型检查, 知道运行期问题才显现出来, 或者线上运行时才能暴露出问题
- 难使用 : 函数的使用者看到函数的时候并不知道你的函数设计, 并不知道应该传入什么类型的数据
如何解决这种动态语言定义的弊端呢 ?
增加文档Documentation String
- 这只是一个惯例, 不是强制性的标准
- 函数定义更新了, 文档未必同步更新
def add(x, y):
"""
:param x: int
:param y: int
:return: int
"""
return x + y
函数注解(Function Annotations)
- Python 3.5引入
- 把函数的参数景行类型注解
- 对函数的返回值进行类型注解
- 只对函数参数做一个辅助说明并不对函数参数进行类型检查
- 提示给第三方工具, 做代码分析, 发现隐藏bug
- 函数注解的信息, 保存在_annotations_属性中
add.__annotations__
栗子 :
def add(x:int, y:int) -> int:
"""
:param x: int
:param y: int
:return: int
"""
return x + y
print(add(4, 5))
print(add('hello', 'world'))
- 在函数参数注解的类型与给定的实参类型不符时, 不会报错, 但是解释器会提示, 把与注释内容不统一的实参(上面的’hello’, ‘world’)标注成黄色
变量注解
-
Python 3.6引入, 注意他只是一种对变量的说明, 非强制
- i : int=3
业务应用
函数参数类型检查
-
思路 :
- 函数参数的检查, 一定是在函数外, 如要把代码侵入到函数中
- 函数应该作为参数传入到检查函数中
- 检查函数拿到函数传入的实际参数, 与形参声明对比
- __annotations__属性是一个字典, 其中包括返回值类型的声明, 假设要做位置参数的判断, 无法和字典中声明对应, 使用inspect 模块
inspect 模块
- 提供获取对象信息的函数, 可以检查函数和类、两类型检查
- signature(callable), 获取签名(函数强命包含了一个函数的信息, 包括函数名、它的类型、它所在的类和名称空间及其他信息)
import inspect
def add(x:int, y:int, *args, **kwargs) -> int:
return x + y
sig = inspect.signature(add)
print(1, sig, type(sig))
print(2, sig.parameters)
print(3, sig.return_annotation)
print(4, sig.parameters['y'], sig.parameters['y'].annotation, type(sig.parameters['y']))
print(5, sig.parameters['args'], sig.parameters['args'].annotation)
print(6, sig.parameters['kwargs'], sig.parameters['kwargs'].annotation)
运行结果
1 (x:int, y:int, *args, **kwargs) -> int <class 'inspect.Signature'>
2 OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
3 <class 'int'>
4 y:int <class 'int'> <class 'inspect.Parameter'>
5 *args <class 'inspect._empty'>
6 **kwargs <class 'inspect._empty'>
- inspect.isfunction(add), 是否是函数
- inspect.ismethod(add), 是否是类的方法
- inspect.isgenerator(add), 是否是生成器对象
- inspect.isclass(add), 是否是类
- inspect.ismodule(add), 是否是模块
- inspect.isbuild(add), 是否是内建对象
- 更对is 函数请查阅inspect 模块帮助
Paramter 对象
-
保存在元祖中, 是只读的
-
name, 参数的名字
-
annotation, 参数的注解, 可能是没有定义
-
default, 参数的缺省值, 可能没有定义
-
empty, 特殊的类, 用来标记default 属性或者注释annotation 属性的空值
-
kind, 实参如何绑定到实参, 就是形参的类型
- POSITIONAL_ONLY, 值必须是位置参数提供
- POSITIONAL_OR_KEYWORD, 值可以作为关键字参数或者位置参数提供
- VAR_POSITIONAL, 可变位置参数, 对应*args
- KEYWORD_ONLY, keyword-only参数, 对应*rags 之后出现的非可变关键字参数
- VAR_KEYWORD, 可变关键字参数, 对应**kwargs
栗子
import inspect
def add(x:int, y:int, *args, **kwargs) -> int:
return x + y
sig = inspect.signature(add)
print(sig.parameters)
print(sig.return_annotation)
for i, item in enumerate(sig.parameters.items()):
name, param = item
print(i, name, param, param.annotation, param.kind, param.default)
运行结果
OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
<class 'int'>
0 x x:int <class 'int'> POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
1 y y:int <class 'int'> POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
2 args *args <class 'inspect._empty'> VAR_POSITIONAL <class 'inspect._empty'>
3 kwargs **kwargs <class 'inspect._empty'> VAR_KEYWORD <class 'inspect._empty'>
有如下函数, 如何对参数进行验证是否符合要求呢 ?
def add(x, y:int=7) -> int:
return x + y
分析
- 调用时, 判断用户输入的实参是否符合要求
- 调用时, 用户感觉上海市调用add 函数(想到了装饰器. .)
- 对用户输入的数据和声明的类型对比, 如果不符, 提示用户
import inspect
def add(x, y:int) -> int:
return x + y
def check(fn):
def wrapper(*args, **kwargs):
sig = inspect.signature(fn)
params = sig.parameters
values = list(params.values())
for i, p in enumerate(args):
if isinstance(p, values[i].annotation):
print('OK')
for k, v in kwargs.items():
if isinstance(v, params[k].annotation):
print('OK')
return fn(*args, **kwargs)
return wrapper
# 调用测试
test1 = check(add)(20, 10)
test2 = check(add)(20, y=10)
test3 = check(add)(y=10, x=20)
print(test1, test2, test3)
运行结果
OK
OK
OK
30 30 30
这里简单实现了参数校验, 但是业务需求是参数有注解要求实参类型和声明类型一致, 没有注解的情况下如何修改代码呢 ?
import inspect
def check(fn):
def wrapper(*args, **kwargs):
sig = inspect.signature(fn)
params = sig.parameters
values = list(params.values())
for i, p in enumerate(args):
param = values[i]
if param.annotation is not param.empty and not isinstance(p, param.annotation):
print(p, '!==', values[i].annotation)
for k, v in kwargs.items():
if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation):
print(k, v, '!===', params[k].annotation)
return fn(*args, **kwargs)
return wrapper
@check
def add(x, y:int) -> int:
return x + y
# 调用测试
test1 = add('a', 'b')
test2 = add('c', y='d')
test3 = add(y=10, x=20)
print(test1, test2, test3)
运行结果
b !== <class 'int'>
y d !=== <class 'int'>
ab cd 30