Python类型注解及inspect模块

前言

  在廖雪峰的Python教程web实战day5——编写web框架中使用inspect模块进行函数的检查,这一部分的代码确实比较复杂。因此,结合网上的文章,将我对python注解及inspect模块的理解分享给大家。

Python类型注解

  在介绍inspect模块之前,先给大家讲解类型注解这个在Python3.5后引入的新特性。
  Python是一门动态语言。相较于静态语言需要在申明变量时写明类型,Python的变量书写更为灵活简单,变量随时可以被赋值,且能赋值为不同类型。但是这也带来了一定的弊端。
首先,用java和python进行展示:

  • Python
def add(x, y):
    return x + y

print(f"传入两个整数得到二者之和:{add(2, 3)}")
print(f"传入了两个字符串,程序依然未报错,结果是:{add('aa', 'bb')}")
print(add(2, "aa"))

输出:

传入两个整数得到二者之和:5
传入了两个字符串,程序依然未报错,结果是:aabb
TypeError: unsupported operand type(s) for +: 'int' and 'str'

  • Java
public class test {

    public static void main(String[] args) {
        System.out.println(add(2, 3));
        System.out.println(add(2, "aa"));
    }

    public static int add(int x, int y) {
        return x + y;
    }
}

输出:

Error:(7, 35) java: 不兼容的类型: java.lang.String无法转换为int

  Java在编译过程中就已经报错,但是Python在运行期间执行到相应语句才会报错。在实际开发中,如果不加以标注,很有可能发生传递错误的参数类型,或者使用这个函数的人根本不知道应该传什么类型的参数。

解决方案
  • 文档注释
def add(x, y):
    """
    :param x: int
    :param y: int
    :return: int
    """
    return x + y

  但是程序员不一定会写注释,或者函数定义改变了,文档注释没有更新。

  • 函数注解
    • python3.5引入的新特性
    • 可以对函数参数、返回值进行类型注解,且只做提示,运行中不做检查
    • 帮助第三方软件做代码分析,如在pycharm中,给上述函数传入一个int和一个str,会有相应提示:Expected type ‘int’, got ‘str’ instead
    • python3.6引入变量注解

示例:

def add(x: int, y: int) -> int:
    """
    :param x: int
    :param y: int
    :return: int
    """
    return x + y

参数类型检查——inspect模块

  inspect模块提供获取对象信息的函数,可以检查函数和类、类型检查。

 * inspect.isfunction(add)    # 是否是函数 
 * inspect.ismethod(add)    # 是否是类的方法
 * inspect.isgenerator(add)    # 是否是生成器对象 
 * inspect.isgeneratorfunction(add)    # 是否是生成器函数 
 * inspect.isclass(add)    # 是否是类 
 * inspect.ismodule(inspect)    # 是否是模块 
 * inspect.isbuiltin(print)    # 是否是内建对象
  • signature(callable):获取签名(函数签名包含一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息)
  • parameter对象:
    • 保存在元组中,是只读的
    • name:参数的名字
    • annotation:参数注解,可能没有定义
    • default:参数的缺省值,可能没有定义
    • empty:特殊的类,用来标记default属性或者注释annotation属性的空值
    • kind:实参如何绑定到形参,就是形参的类型
    • POSITIONAL_ONLY:值必须是位置参提供
    • POSITIONAL_OR_KEYWORD:值可以作为关键字或者位置参数提供
    • VAR_POSITIONAL:可变位置参数,对应*args
    • KEYWORD_ONLY:keyword-only参数,对应或者args之后的出现的非可变关键字参数
    • VAR_KEYWORD:可变关键字参数,对应**kwargs

示例:

import inspect


def func(a, b=0, *c, d, e=1, **f):
    pass

aa = inspect.signature(func)
print(f"inspect.signature(fn)是:{aa}")
print(f"inspect.signature(fn)的类型:{type(aa)}")
print(f"=" * 100)

bb = aa.parameters
print(f"signature.paramerters属性是:{bb}")
print(f"signature.paramerters属性的类型是{type(bb)}")
print(f"bb.items是{bb.items}")
print(f"=" * 100)

for cc, dd in bb.items():
    print(f"mappingproxy.items()返回的两个值分别是:{cc}和{dd}")
    print(f"mappingproxy.items()返回的两个值的类型分别是:{type(cc)}和{type(dd)}")
    ee = dd.kind
    print(f"Parameter.kind属性是:{ee}")
    print(f"Parameter.kind属性的类型是:{type(ee)}")
    gg = dd.default
    print(f"Parameter.default的值是: {gg}")
    print(f"Parameter.default的属性是: {type(gg)}")
    print(f"=" * 100)

ff = inspect.Parameter.KEYWORD_ONLY
print(f"inspect.Parameter.KEYWORD_ONLY的值是:{ff}")
print(f"inspect.Parameter.KEYWORD_ONLY的类型是:{type(ff)}")

输出:

inspect.signature(fn)是:(a, b=0, *c, d, e=1, **f)
inspect.signature(fn)的类型:<class 'inspect.Signature'>
====================================================================================================
signature.paramerters属性是:OrderedDict([('a', <Parameter "a">), ('b', <Parameter "b=0">), ('c', <Parameter "*c">), ('d', <Parameter "d">), ('e', <Parameter "e=1">), ('f', <Parameter "**f">)])
signature.paramerters属性的类型是<class 'mappingproxy'>
bb.items是<built-in method items of mappingproxy object at 0x000002845BCB1B28>
====================================================================================================
mappingproxy.items()返回的两个值分别是:a和a
mappingproxy.items()返回的两个值的类型分别是:<class 'str'>和<class 'inspect.Parameter'>
Parameter.kind属性是:1
Parameter.kind属性的类型是:<enum '_ParameterKind'>
Parameter.default的值是: <class 'inspect._empty'>
Parameter.default的属性是: <class 'type'>
====================================================================================================
mappingproxy.items()返回的两个值分别是:b和b=0
mappingproxy.items()返回的两个值的类型分别是:<class 'str'>和<class 'inspect.Parameter'>
Parameter.kind属性是:1
Parameter.kind属性的类型是:<enum '_ParameterKind'>
Parameter.default的值是: 0
Parameter.default的属性是: <class 'int'>
====================================================================================================
mappingproxy.items()返回的两个值分别是:c和*c
mappingproxy.items()返回的两个值的类型分别是:<class 'str'>和<class 'inspect.Parameter'>
Parameter.kind属性是:2
Parameter.kind属性的类型是:<enum '_ParameterKind'>
Parameter.default的值是: <class 'inspect._empty'>
Parameter.default的属性是: <class 'type'>
====================================================================================================
mappingproxy.items()返回的两个值分别是:d和d
mappingproxy.items()返回的两个值的类型分别是:<class 'str'>和<class 'inspect.Parameter'>
Parameter.kind属性是:3
Parameter.kind属性的类型是:<enum '_ParameterKind'>
Parameter.default的值是: <class 'inspect._empty'>
Parameter.default的属性是: <class 'type'>
====================================================================================================
mappingproxy.items()返回的两个值分别是:e和e=1
mappingproxy.items()返回的两个值的类型分别是:<class 'str'>和<class 'inspect.Parameter'>
Parameter.kind属性是:3
Parameter.kind属性的类型是:<enum '_ParameterKind'>
Parameter.default的值是: 1
Parameter.default的属性是: <class 'int'>
====================================================================================================
mappingproxy.items()返回的两个值分别是:f和**f
mappingproxy.items()返回的两个值的类型分别是:<class 'str'>和<class 'inspect.Parameter'>
Parameter.kind属性是:4
Parameter.kind属性的类型是:<enum '_ParameterKind'>
Parameter.default的值是: <class 'inspect._empty'>
Parameter.default的属性是: <class 'type'>
====================================================================================================
inspect.Parameter.KEYWORD_ONLY的值是:3
inspect.Parameter.KEYWORD_ONLY的类型是:<enum '_ParameterKind'>

通过装饰器检查函数:
有函数如下:

def add(x, y:int=8) -> int:
    return x + y
我们希望检查用户输入是否符合参数注解的要求。
调用时,判断用户输入的实参是否符合要求
调用时,用户感觉上还是在调用add函数
对用户输入的数据和声明的类型进行对比,如果不符合,提示用户
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from functools import wraps
import inspect


def checker(fn):
    @wraps(fn)  # Decorator factory
    def wrapper(*args, **kwargs):
        # 检查实参
        # print(f"args: {args}, kwargs: {kwargs}")
        sig = inspect.signature(fn)
        params = sig.parameters  # 有序字典
        # 位置参数检查
        # print(f"params.keys(): {params.keys()}")
        param_tuple = tuple(params.keys())   # 转换成有序的元组
        # print(f"有序元组: {param_tuple}")
        for i, v in enumerate(args):
            k = param_tuple[i]   # 拿到key
            # print(f"k: {k}")
            if not isinstance(v, params[k].annotation):  # 根据key拿到注解
                print(f"{v} is not {params[k].annotation}")
        # 关键参数检查
        for k,v in kwargs.items():
            if not isinstance(v, params[k].annotation):
                errstr = f"{v} is not {params[k].annotation}"
                print(f"{v} is not {params[k].annotation}")
                raise TypeError(errstr)   # 抛出异常
        result = fn(*args, **kwargs)
        return result
    return wrapper

@checker
def add(x:int, y:int = 8) -> int:
    return x + y

print(add(4, y=5))
print("=" * 50)
add3("abc", "bcd")
print("=" * 50)
print(add(4))

输出:

9
==================================================
abc is not <class 'int'>
bcd is not <class 'int'>
==================================================
12

小结

  在廖雪峰教程的coroweb.py中有以下代码:

class RequestHandler(object):

    def __init__(self, app, fn):
        self._app = app
        self._func = fn
        self._has_request_arg = has_request_arg(fn)
        self._has_var_kw_arg = has_var_kw_arg(fn)
        self._has_named_kw_args = has_named_kw_args(fn)
        self._named_kw_args = get_named_kw_args(fn)
        self._required_kw_args = get_required_kw_args(fn)

  其中定义的各个函数就利用了inspect函数对参数进行了检测。
  我对教程中的代码进行了更新,重写了部分代码,比如asyncio新的语法及部分函数调用,并且我也对代码进行了详细的注释,相信大家能够通过注释去更好地理解web框架。
代码链接戳我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值