我应该强制Python类型检查吗?

fra*_*lin 11 python types semantics

也许作为我使用强类型语言(Java)的日子的残余,我经常发现自己编写函数然后强制进行类型检查.例如:

def orSearch(d, query):
    assert (type(d) == dict)
    assert (type(query) == list)
Run Code Online (Sandbox Code Playgroud)

我应该继续这样做吗?做/不做这有什么好处?

小智 9

在大多数情况下,它会干扰鸭子打字和遗传.

  • 继承:你当然打算写一些具有效果的东西

    assert isinstance(d, dict)
    
    Run Code Online (Sandbox Code Playgroud)

    确保您的代码也能正常使用子类dict.我认为这类似于Java中的用法.但Python有一些Java没有的东西,即

  • Duck输入:大多数内置函数不要求对象属于特定类,只要它具有以正确方式运行的某些成员函数.的for环,例如,是否只要求该循环变量是一个迭代,这意味着它具有的成员函数__iter__()next(),并且它们的行为正常.

因此,如果您不想关闭Python的全部功能,请不要检查生产代码中的特定类型.(尽管如此,它可能对调试很有用.)


Mar*_*cin 8

别那样做.

使用"动态"语言(强烈键入值*,无类型变量和后期绑定)的关键是你的函数可以正确多态,因为它们将处理任何支持接口的对象功能依赖("鸭子打字").

Python定义了许多不同类型的对象可以实现而不相互关联的通用协议(例如,可迭代的).协议本身不是一种语言功能(与java接口不同).

这样做的实际结果是,一般来说,只要你理解了你的语言类型,并且你恰当地评论(包括文档字符串,所以其他人也理解程序中的类型),你通常可以编写更少的代码,因为您不必围绕您的类型系统进行编码.你最终不会为不同的类型编写相同的代码,只是使用不同的类型声明(即使这些类是不相关的层次结构),你也不必弄清楚哪些转换是安全的,哪些不是,如果你想要尝试只编写一段代码.

还有其他语言在理论上提供相同的东西:类型推断语言.最流行的是C++(使用模板)和Haskell.理论上(可能在实践中),您最终可以编写更少的代码,因为类型是静态解析的,因此您不必编写异常处理程序来处理传递错误的类型.我发现他们仍然需要你编程到类型系统,而不是你的程序中的实际类型(他们的类型系统是定理证明,并且易于处理,他们不分析你的整个程序).如果这听起来不错,请考虑使用其中一种语言而不是python(或ruby,smalltalk或任何lisp变体).

在python(或任何类似的动态语言)中,您将希望在对象不支持特定方法时使用异常来代替类型测试.在这种情况下,要么让它上升,要么抓住它,并提出关于不正确类型的异常.这种类型的"更好地请求宽恕而非许可"编码是惯用的python,并且极大地促进了更简单的代码.

*在实践中.Python和Smalltalk中的类更改是可能的,但很少见.它也与使用低级语言进行投射不同.


Noc*_*wer 5

如果您坚持在代码中添加类型检查,您可能需要研究注释以及它们如何简化您必须编写的内容。StackOverflow 上的一个问题介绍了一个利用注释的小型、模糊的类型检查器。这是一个基于您的问题的示例:

>>> def statictypes(a):
    def b(a, b, c):
        if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c)))
        return c
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))

>>> @statictypes
def orSearch(d: dict, query: dict) -> type(None):
    pass

>>> orSearch({}, {})
>>> orSearch([], {})
Traceback (most recent call last):
  File "<pyshell#162>", line 1, in <module>
    orSearch([], {})
  File "<pyshell#155>", line 5, in <lambda>
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
  File "<pyshell#155>", line 5, in <listcomp>
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
  File "<pyshell#155>", line 3, in b
    if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c)))
TypeError: d should be <class 'dict'>, not <class 'list'>
>>> orSearch({}, [])
Traceback (most recent call last):
  File "<pyshell#163>", line 1, in <module>
    orSearch({}, [])
  File "<pyshell#155>", line 5, in <lambda>
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
  File "<pyshell#155>", line 5, in <listcomp>
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
  File "<pyshell#155>", line 3, in b
    if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c)))
TypeError: query should be <class 'dict'>, not <class 'list'>
>>> 
Run Code Online (Sandbox Code Playgroud)

您可能会看着类型检查器并想知道,“它到底在做什么?” 我决定自己找出答案并将其转化为可读的代码。第二稿取消了该b功能(您可以称其为verify)。第三个也是最后一个草案做了一些改进,如下所示供您使用:

import functools

def statictypes(func):
    template = '{} should be {}, not {}'
    @functools.wraps(func)
    def wrapper(*args):
        for name, arg in zip(func.__code__.co_varnames, args):
            klass = func.__annotations__.get(name, object)
            if not isinstance(arg, klass):
                raise TypeError(template.format(name, klass, type(arg)))
        result = func(*args)
        klass = func.__annotations__.get('return', object)
        if not isinstance(result, klass):
            raise TypeError(template.format('return', klass, type(result)))
        return result
    return wrapper
Run Code Online (Sandbox Code Playgroud)

编辑:

自从写这个答案以来已经过去了四年多了,从那时起 Python 发生了很多变化。由于这些变化和个人在语言方面的成长,重新审视类型检查代码并重写它以利用新功能和改进的编码技术似乎是有益的。statictypes因此,提供了以下修订,对(现已重命名)函数装饰器进行了一些边际改进static_types

#! /usr/bin/env python3
import functools
import inspect


def static_types(wrapped):
    def replace(obj, old, new):
        return new if obj is old else obj

    signature = inspect.signature(wrapped)
    parameter_values = signature.parameters.values()
    parameter_names = tuple(parameter.name for parameter in parameter_values)
    parameter_types = tuple(
        replace(parameter.annotation, parameter.empty, object)
        for parameter in parameter_values
    )
    return_type = replace(signature.return_annotation, signature.empty, object)

    @functools.wraps(wrapped)
    def wrapper(*arguments):
        for argument, parameter_type, parameter_name in zip(
            arguments, parameter_types, parameter_names
        ):
            if not isinstance(argument, parameter_type):
                raise TypeError(f'{parameter_name} should be of type '
                                f'{parameter_type.__name__}, not '
                                f'{type(argument).__name__}')
        result = wrapped(*arguments)
        if not isinstance(result, return_type):
            raise TypeError(f'return should be of type '
                            f'{return_type.__name__}, not '
                            f'{type(result).__name__}')
        return result
    return wrapper
Run Code Online (Sandbox Code Playgroud)