在Python 3中实现类型检查器的简单方法


Posted in Python onJuly 03, 2015

示例函数

为了开发类型检查器,我们需要一个简单的函数对其进行实验。欧几里得算法就是一个完美的例子:
 

def gcd(a, b):
  
'''Return the greatest common divisor of a and b.'''
  a = abs(a)
  b = abs(b)
  if a < b:
    a, b = b, a
  while b != 0:
    a, b = b, a % b
  return a

在上面的示例中,参数 a 和 b 以及返回值应该是 int 类型的。预期的类型将会以函数注解的形式来表达,函数注解是 Python 3 的一个新特性。接下来,类型检查机制将会以一个装饰器的形式实现,注解版本的第一行代码是:
 

def gcd(a: int, b: int) -> int:

使用“gcd.__annotations__”可以获得一个包含注解的字典:
 

>>> gcd.__annotations__
{'return': <class 'int'>, 'b': <class 'int'>, 'a': <class 'int'>}
>>> gcd.__annotations__['a']
<class 'int'>

需要注意的是,返回值的注解存储在键“return”下。这是有可能的,因为“return”是一个关键字,所以不能用作一个有效的参数名。
检查返回值类型

返回值注解存储在字典“__annotations__”中的“return”键下。我们将使用这个值来检查返回值(假设注解存在)。我们将参数传递给原始函数,如果存在注解,我们将通过注解中的值来验证其类型:
 

def typecheck(f):
  def wrapper(*args, **kwargs):
    result = f(*args, **kwargs)
    return_type = f.__annotations__.get('return', None)
    if return_type and not isinstance(result, return_type):
      raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__))
    return result
  return wrapper

我们可以用“a”替换函数gcd的返回值来测试上面的代码:

Traceback (most recent call last):
 File "typechecker.py", line 9, in <module>
  gcd(1, 2)
 File "typechecker.py", line 5, in wrapper
  raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__))
RuntimeError: gcd should return int

由上面的结果可知,确实检查了返回值的类型。
检查参数类型

函数的参数存在于关联代码对象的“co_varnames”属性中,在我们的例子中是“gcd.__code__.co_varnames”。元组包含了所有局部变量的名称,并且该元组以参数开始,参数数量存储在“co_nlocals”中。我们需要遍历包括索引在内的所有变量,并从参数“args”中获取参数值,最后对其进行类型检查。

得到了下面的代码:
 

def typecheck(f):
  def wrapper(*args, **kwargs):
    for i, arg in enumerate(args[:f.__code__.co_nlocals]):
      name = f.__code__.co_varnames[i]
      expected_type = f.__annotations__.get(name, None)
      if expected_type and not isinstance(arg, expected_type):
        raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__))
    result = f(*args, **kwargs)
    return_type = f.__annotations__.get('return', None)
    if return_type and not isinstance(result, return_type):
      raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__))
    return result
  return wrapper

在上面的循环中,i是数组args中参数的以0起始的索引,arg是包含其值的字符串。可以利用“f.__code__.co_varnames[i]”读取到参数的名称。类型检查代码与返回值类型检查完全一样(包括错误消息的异常)。

为了对关键字参数进行类型检查,我们需要遍历参数kwargs。此时的类型检查几乎与第一个循环中相同:
 

for name, arg in kwargs.items():
  expected_type = f.__annotations__.get(name, None)
  if expected_type and not isinstance(arg, expected_type):
    raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__))

得到的装饰器代码如下:
 

def typecheck(f):
  def wrapper(*args, **kwargs):
    for i, arg in enumerate(args[:f.__code__.co_nlocals]):
      name = f.__code__.co_varnames[i]
      expected_type = f.__annotations__.get(name, None)
      if expected_type and not isinstance(arg, expected_type):
        raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__))
    for name, arg in kwargs.items():
      expected_type = f.__annotations__.get(name, None)
      if expected_type and not isinstance(arg, expected_type):
        raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__))
    result = f(*args, **kwargs)
    return_type = f.__annotations__.get('return', None)
    if return_type and not isinstance(result, return_type):
      raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__))
    return result
  return wrapper

将类型检查代码写成一个函数将会使代码更加清晰。为了简化代码,我们修改错误信息,而当返回值是无效的类型时,将会使用到这些错误信息。我们也可以利用 functools 模块中的 wraps 方法,将包装函数的一些属性复制到 wrapper 中(这使得 wrapper 看起来更像原来的函数):
 

def typecheck(f):
  def do_typecheck(name, arg):
    expected_type = f.__annotations__.get(name, None)
    if expected_type and not isinstance(arg, expected_type):
      raise RuntimeError("{} should be of type {} instead of {}".format(name, expected_type.__name__, type(arg).__name__))
 
  @functools.wraps(f)
  def wrapper(*args, **kwargs):
    for i, arg in enumerate(args[:f.__code__.co_nlocals]):
      do_typecheck(f.__code__.co_varnames[i], arg)
    for name, arg in kwargs.items():
      do_typecheck(name, arg)
 
    result = f(*args, **kwargs)
 
    do_typecheck('return', result)
    return result
  return wrapper

结论

注解是 Python 3 中的一个新元素,本文例子中的使用方法很普通,你也可以想象很多特定领域的应用。虽然上面的实现代码并不能满足实际产品要求,但它的目的本来就是用作概念验证。可以对其进行以下改善:

  •     处理额外的参数( args 中意想不到的项目)
  •     默认值类型检查
  •     支持多个类型
  •     支持模板类型(例如,int 型列表)
Python 相关文章推荐
Python设置Socket代理及实现远程摄像头控制的例子
Nov 13 Python
Python中optparser库用法实例详解
Jan 26 Python
python保存log日志,实现用log日志画图
Dec 24 Python
在matplotlib中改变figure的布局和大小实例
Apr 23 Python
深入了解python列表(LIST)
Jun 08 Python
Django如何批量创建Model
Sep 01 Python
Python HTMLTestRunner如何下载生成报告
Sep 04 Python
Python classmethod装饰器原理及用法解析
Oct 17 Python
python爬虫如何解决图片验证码
Feb 14 Python
Python数据可视化之绘制柱状图和条形图
May 25 Python
用Python将GIF动图分解成多张静态图片
Jun 11 Python
关于Python OS模块常用文件/目录函数详解
Jul 01 Python
python插入排序算法实例分析
Jul 03 #Python
python列出目录下指定文件与子目录的方法
Jul 03 #Python
python清除字符串里非字母字符的方法
Jul 02 #Python
python清除字符串里非数字字符的方法
Jul 02 #Python
python实现在控制台输入密码不显示的方法
Jul 02 #Python
python获取外网ip地址的方法总结
Jul 02 #Python
python实现将英文单词表示的数字转换成阿拉伯数字的方法
Jul 02 #Python
You might like
php的header和asp中的redirect比较
2006/10/09 PHP
coreseek 搜索英文的问题详解
2013/06/08 PHP
ThinkPHP访问不存在的模块跳转到404页面的方法
2014/06/19 PHP
PHP 中魔术常量的实例详解
2017/10/26 PHP
php设计模式之工厂模式用法经典实例分析
2019/09/20 PHP
JS判断两个时间大小的示例代码
2014/01/28 Javascript
js的touch事件的实际引用
2014/10/13 Javascript
微信小程序(三):网络请求
2017/01/13 Javascript
Angular 4依赖注入学习教程之InjectToken的使用(八)
2017/06/04 Javascript
JavaScript简单实现关键字文本搜索高亮显示功能示例
2018/07/25 Javascript
Vue2实时监听表单变化的示例讲解
2018/08/30 Javascript
vue移动端下拉刷新和上拉加载的实现代码
2018/09/08 Javascript
vue中keep-alive组件的入门使用教程
2019/06/06 Javascript
ionic2.0双击返回键退出应用
2019/09/17 Javascript
vue实现pdf文档在线预览功能
2019/11/26 Javascript
easyUI 实现的后台分页与前台显示功能示例
2020/06/01 Javascript
vue如何使用外部特殊字体的操作
2020/07/30 Javascript
[51:11]2014 DOTA2国际邀请赛中国区预选赛5.21 LGD-CDEC VS DT
2014/05/22 DOTA
Python实现PS图像调整黑白效果示例
2018/01/25 Python
python随机在一张图像上截取任意大小图片的方法
2019/01/24 Python
python格式化输出保留2位小数的实现方法
2019/07/02 Python
Win10下python 2.7与python 3.7双环境安装教程图解
2019/10/12 Python
用python制作个视频下载器
2021/02/01 Python
加拿大在线隐形眼镜专家:PerfectLens.ca
2016/11/19 全球购物
泰国的头号网上婴儿用品店:Motherhood.co.th
2019/04/09 全球购物
美赞臣新加坡官方旗舰店:Enfagrow新加坡
2019/05/15 全球购物
生物技术专业毕业生求职信范文
2013/12/14 职场文书
初中班主任经验交流材料
2014/05/16 职场文书
主要负责人任命书
2014/06/06 职场文书
小学校本培训方案
2014/06/06 职场文书
大学班干部竞选稿
2015/11/20 职场文书
2016保送生自荐信范文
2016/01/29 职场文书
创业计划书之美容店
2019/09/16 职场文书
深入理解Pytorch微调torchvision模型
2021/11/11 Python
Android Studio实现带三角函数对数运算功能的高级计算器
2022/05/20 Java/Android
Echarts如何重新渲染实例详解
2022/05/30 Javascript