Python 3 和静态类型
我之前没有太关注Python 3的发展,最近才注意到一些有趣的新语法变化。特别是从这个回答中看到的函数参数注解。
def digits(x:'nonnegative number') -> "yields number's digits":
# ...
我对这个完全不了解,心想这可能可以用来在Python中实现静态类型检查!
经过一番搜索,发现关于Python中(完全可选的)静态类型的讨论还挺多的,比如在PEP 3107和“为Python添加可选静态类型”(还有第二部分)中都有提到。
不过,我不太清楚这个进展到什么程度了。有没有使用参数注解的静态类型实现?这些参数化类型的想法有没有被引入到Python 3中?
5 个回答
在Python中,“静态类型”只能在运行时进行类型检查,这样会导致应用程序变慢。因此,通常情况下你不希望这样做。相反,你可能希望某些方法检查它们的输入。这可以通过简单的断言来轻松实现,或者如果你(错误地)认为需要频繁使用,也可以使用装饰器。
还有一种替代静态类型检查的方法,那就是使用面向方面的组件架构,比如Zope组件架构。与其检查类型,不如对其进行适配。所以,不是这样:
assert isinstance(theobject, myclass)
你可以这样做:
theobject = IMyClass(theobject)
如果这个对象已经实现了IMyClass,那么什么都不会发生。如果没有实现,就会查找一个适配器,把这个对象包装成IMyClass。如果找不到适配器,就会出现错误。
这种方法将Python的动态特性与对特定类型的需求结合在了一起。
在那个PEP中提到,静态类型检查是函数注解可以用来的一种应用,但具体怎么做就留给第三方库来决定了。也就是说,Python核心部分不会有官方的实现。
至于第三方的实现,有一些代码片段(比如http://code.activestate.com/recipes/572161/),看起来效果还不错。
编辑:
我想补充一下,检查行为比检查类型更好,所以我觉得静态类型检查并不是个特别好的主意。我上面的回答是为了回应问题,并不是说我会以那种方式进行类型检查。
谢谢你阅读我的代码!
其实,在Python中创建一个通用的注解检查器并不难。这是我的看法:
'''Very simple enforcer of type annotations.
This toy super-decorator can decorate all functions in a given module that have
annotations so that the type of input and output is enforced; an AssertionError is
raised on mismatch.
This module also has a test function func() which should fail and logging facility
log which defaults to print.
Since this is a test module, I cut corners by only checking *keyword* arguments.
'''
import sys
log = print
def func(x:'int' = 0) -> 'str':
'''An example function that fails type checking.'''
return x
# For simplicity, I only do keyword args.
def check_type(*args):
param, value, assert_type = args
log('Checking {0} = {1} of {2}.'.format(*args))
if not isinstance(value, assert_type):
raise AssertionError(
'Check failed - parameter {0} = {1} not {2}.'
.format(*args))
return value
def decorate_func(func):
def newf(*args, **kwargs):
for k, v in kwargs.items():
check_type(k, v, ann[k])
return check_type('<return_value>', func(*args, **kwargs), ann['return'])
ann = {k: eval(v) for k, v in func.__annotations__.items()}
newf.__doc__ = func.__doc__
newf.__type_checked = True
return newf
def decorate_module(module = '__main__'):
'''Enforces type from annotation for all functions in module.'''
d = sys.modules[module].__dict__
for k, f in d.items():
if getattr(f, '__annotations__', {}) and not getattr(f, '__type_checked', False):
log('Decorated {0!r}.'.format(f.__name__))
d[k] = decorate_func(f)
if __name__ == '__main__':
decorate_module()
# This will raise AssertionError.
func(x = 5)
考虑到这一点,乍一看,这个东西不流行似乎有点奇怪。不过,我相信有很好的理由说明它并没有看起来那么有用。一般来说,类型检查是有帮助的,因为如果你把整数和字典加在一起,很可能你犯了明显的错误(即使你有合理的意图,明确表达总比模糊好)。
但在现实生活中,你经常会混合同一种计算机类型(编译器看到的)但明显不同的人类类型,例如下面这段代码就包含了一个明显的错误:
height = 1.75 # Bob's height in meters.
length = len(sys.modules) # Number of modules imported by program.
area = height * length # What's that supposed to mean???
任何人只要知道变量 height
和 length
的人类类型,就应该立刻发现上面这一行的错误,尽管在计算机看来,这看起来是一个完全合法的整数和浮点数的乘法。
关于这个问题的可能解决方案还有很多,但强制执行“计算机类型”显然只是一个半解决方案,所以,至少在我看来,这比没有解决方案还要糟糕。这就是为什么系统匈牙利命名法是个糟糕主意,而应用匈牙利命名法是个好主意的原因。更多信息可以参考Joel Spolsky的文章。
现在,如果有人能实现一种Python的第三方库,能够自动为现实世界的数据分配其人类类型,然后处理这种类型的转换,比如 width * height -> area
,并通过函数注解来强制检查,我觉得这将是人们真正可以用到的类型检查!