Python 3 和静态类型

64 投票
5 回答
33393 浏览
提问于 2025-04-15 13:35

我之前没有太关注Python 3的发展,最近才注意到一些有趣的新语法变化。特别是从这个回答中看到的函数参数注解。

def digits(x:'nonnegative number') -> "yields number's digits":
    # ...

我对这个完全不了解,心想这可能可以用来在Python中实现静态类型检查!

经过一番搜索,发现关于Python中(完全可选的)静态类型的讨论还挺多的,比如在PEP 3107“为Python添加可选静态类型”(还有第二部分)中都有提到。

不过,我不太清楚这个进展到什么程度了。有没有使用参数注解的静态类型实现?这些参数化类型的想法有没有被引入到Python 3中?

5 个回答

14

在Python中,“静态类型”只能在运行时进行类型检查,这样会导致应用程序变慢。因此,通常情况下你不希望这样做。相反,你可能希望某些方法检查它们的输入。这可以通过简单的断言来轻松实现,或者如果你(错误地)认为需要频繁使用,也可以使用装饰器。

还有一种替代静态类型检查的方法,那就是使用面向方面的组件架构,比如Zope组件架构。与其检查类型,不如对其进行适配。所以,不是这样:

assert isinstance(theobject, myclass)

你可以这样做:

theobject = IMyClass(theobject)

如果这个对象已经实现了IMyClass,那么什么都不会发生。如果没有实现,就会查找一个适配器,把这个对象包装成IMyClass。如果找不到适配器,就会出现错误。

这种方法将Python的动态特性与对特定类型的需求结合在了一起。

15

在那个PEP中提到,静态类型检查是函数注解可以用来的一种应用,但具体怎么做就留给第三方库来决定了。也就是说,Python核心部分不会有官方的实现。

至于第三方的实现,有一些代码片段(比如http://code.activestate.com/recipes/572161/),看起来效果还不错。

编辑:

我想补充一下,检查行为比检查类型更好,所以我觉得静态类型检查并不是个特别好的主意。我上面的回答是为了回应问题,并不是说我会以那种方式进行类型检查。

35

谢谢你阅读我的代码!

其实,在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???

任何人只要知道变量 heightlength人类类型,就应该立刻发现上面这一行的错误,尽管在计算机看来,这看起来是一个完全合法的整数和浮点数的乘法。

关于这个问题的可能解决方案还有很多,但强制执行“计算机类型”显然只是一个半解决方案,所以,至少在我看来,这比没有解决方案还要糟糕。这就是为什么系统匈牙利命名法是个糟糕主意,而应用匈牙利命名法是个好主意的原因。更多信息可以参考Joel Spolsky的文章

现在,如果有人能实现一种Python的第三方库,能够自动为现实世界的数据分配其人类类型,然后处理这种类型的转换,比如 width * height -> area,并通过函数注解来强制检查,我觉得这将是人们真正可以用到的类型检查!

撰写回答