问题

python语言是一门易学易用的语言,不过相比起c++、java语言,就是读别人的代码,是比较痛苦的,尤其代码量比较大的情况,更是如此。

 

def add(a, b):
    return a + b

print(10,20)
print('10','20')

问题的根本原因,就是python中一切皆是对象,函数丢失了类型信息,读起来相当痛苦,只有执行过程,才比较方便掌握代码。

解决方案

方法一,类型注释

python语言缔造者,也已经看出来了代码中没有类型信息的问题。当下的python3版本,已经提供了类型标记。比如:

 

def add(a:int, b:int)->int:
    return a + b

上述代码中,add表示两个整数的和。不过,这个特性,可以理解为是一种注释,实际代码运行的时候,同样还可以传递字符串的参数。

在python3.6滞后的版本,建议坚持用这种写法写python代码,毕竟代码还是要给阅读的,不带类型信息,读起来缺失很费劲。

方法二,类型装饰器

python有个装饰器特性,装饰器可以轻松实现面向切面的编程,比如函数的运行时间统计,函数出入口日志打印等等。代码如下:

 

from functools import wraps
from inspect import signature

def type_assert(*type_args, **type_kwargs):
    def check(func):
        sig = signature(func)
        bound_types = sig.bind_partial(*type_args, **type_kwargs).arguments

        @wraps(func)
        def wrapper(*args, **kwargs):
            bound_values = sig.bind(*args, **kwargs)
            for name, value in bound_values.arguments.items():
                if name not in bound_types:
                    continue

                if not isinstance(value, bound_types[name]):
                    raise TypeError('argument {} must be {}'.format(
                        name, bound_types[name]))
            return func(*args, **kwargs)

        return wrapper

    return check

测试代码如下:

 

# 可以不指定参数使用。@type_assert(int,int)
@type_assert(a=int, b=int))
def add(a, b):
    return a + b

print(add(10, 10))
print(add(10.0, 10.0))

运行结果如下,类型信息在运行时得到有效检查。

 

20
Traceback (most recent call last):
  File "/Users/xxx/data/code/python/type_assert.py", line 38, in <module>
    print(add(10.0, 10.0))
  File "/Users/xxx/data/code/python/type_assert.py", line 18, in wrapper
    raise TypeError('argument {} must be {}'.format(
TypeError: argument a must be <class 'int'>

同样,类型type_assert还可以为参数指定多种类型,完全不用修改代码。比如:

 

@type_assert(a=(int, float), b=(int, float))
def add(a, b):
    return a + b

print(add2(10, 10))
print(add2(10.0, 10.0))

并且,类型type_assert还可以用于类的方法上,进行类型检查。只不过,因为类的方法有个self参数,指定参数的时候需要写明参数名称(当然,self是不用指定类型)。比如:

 

class Math:
    @type_assert(a=int, b=int)
    def add(self, a, b):
        return a + b

math = Math()
math.add(10, 20)
math.add(10.0, 20.0)

输出:

 

Traceback (most recent call last):
  File "/Users/xxx/data/code/python/type_assert.py", line 40, in <module>
    math.add(10.0, 20.0)
  File "/Users/xxx/data/code/python/type_assert.py", line 18, in wrapper
    raise TypeError('argument {} must be {}'.format(
TypeError: argument a must be <class 'int'>

这个方法看上去还比较不错,简单实用。但也不是万能的,不建议到处用,毕竟会降低性能,建议公开给外部调用的方法上使用类型断言。另外,对于类型检查比较严格时,是需要自己处理的。比如下面的代码:

 

@type_assert(a=(int, float, str), b=(int, float, str))
def add(a, b):
    return a + b
add(10.0,'20.0')

这个运行时,因为类型不同引起的异常,type_assert是检查不出来的。如果要支持,就需要在add函数中检查不同类型,该如何处理了。