在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  连接字符串(join %)
Sep 06 Python
Python中下划线的使用方法
Mar 27 Python
python中sys.argv参数用法实例分析
May 20 Python
Python中exit、return、sys.exit()等使用实例和区别
May 28 Python
Python语言实现百度语音识别API的使用实例
Dec 13 Python
Python基于Floyd算法求解最短路径距离问题实例详解
May 16 Python
PyCharm代码格式调整方法
May 23 Python
PyCharm设置护眼背景色的方法
Oct 29 Python
python ubplot使用方法解析
Jan 10 Python
Python reques接口测试框架实现代码
Jul 28 Python
如何利用Python动态模拟太阳系运转
Sep 04 Python
使用Python操作MySQL的小技巧
Sep 10 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 fckeditor 调用的函数
2009/06/21 PHP
PHP中利用sleep函数实现定时执行功能实现代码
2016/08/25 PHP
Yii2实现增删改查后留在当前页的方法详解
2017/01/13 PHP
in.js 一个轻量级的JavaScript颗粒化模块加载和依赖关系管理解决方案
2011/07/26 Javascript
Javascript 页面模板化很多人没有使用过的方法
2012/06/05 Javascript
JS中如何判断传过来的JSON数据中是否存在某字段
2014/08/18 Javascript
jQuery构造函数init参数分析续
2015/05/13 Javascript
IE中document.createElement的iframe无法设置属性name的解决方法
2015/09/14 Javascript
AngularJS表单和输入验证实例
2016/11/02 Javascript
JavaScript实现替换字符串中最后一个字符的方法
2017/03/07 Javascript
详解Windows下安装Nodejs步骤
2017/05/18 NodeJs
JS获取填报扩展单元格控件的值的解决办法
2017/07/14 Javascript
使用JavaScript实现在页面中显示距离2017年中秋节的天数
2017/09/26 Javascript
使用axios实现上传图片进度条功能
2017/12/21 Javascript
JS实现获取word文档内容并输出显示到html页面示例
2018/06/23 Javascript
vue的列表交错过渡实现代码示例
2019/05/05 Javascript
vue移动端城市三级联动组件使用详解
2019/07/26 Javascript
JS实现网站吸顶条
2020/01/08 Javascript
Websocket 向指定用户发消息的方法
2020/01/09 Javascript
PyTorch: 梯度下降及反向传播的实例详解
2019/08/20 Python
使用pygame编写Flappy bird小游戏
2020/03/14 Python
python中scrapy处理项目数据的实例分析
2020/11/22 Python
HTML5 Canvas锯齿图代码实例
2014/04/10 HTML / CSS
Html5插件教程之添加浏览器放大镜效果的商品橱窗
2016/01/07 HTML / CSS
html5 初试 indexedDB(推荐)
2016/07/21 HTML / CSS
日本最大的购物网站乐天市场国际版:Rakuten Global Market(支持中文)
2020/02/03 全球购物
澳大利亚在线床零售商:Bedworks
2020/09/01 全球购物
环境科学毕业生自荐信
2013/11/21 职场文书
酒店经理职责
2014/01/30 职场文书
党的群众路线教育实践活动通讯稿
2014/09/10 职场文书
找规律教学反思
2016/02/23 职场文书
2019年入党思想汇报
2019/03/25 职场文书
2019升学宴主持词范本5篇
2019/10/09 职场文书
详解PHP Swoole与TCP三次握手
2021/05/27 PHP
Mysql存储过程、触发器、事件调度器使用入门指南
2022/01/22 MySQL
Go语言 详解net的tcp服务
2022/04/14 Golang