Python 类型注解及业务应用 - inspect模块

函数定义的弊端

  • 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

你可能感兴趣的:(Python,inspect,模块,参数检查,类型注解,业务应用)