前言
在廖雪峰的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框架。
代码链接戳我