如何在Python 3.6中使用类型提示?

60

我注意到Python 3.5和Python 3.6增加了很多关于静态类型检查的功能,所以我尝试了以下代码(在python 3.6稳定版本中)。

from typing import List

a: List[str] = []
a.append('a')
a.append(1)
print(a)
我惊讶的是,Python 没有给我报错或警告,尽管一个应该只包含字符串的列表中添加了 1。Pycharm 检测到了类型错误并发出了警告,但这并不明显,也没有显示在输出控制台中,我担心有时可能会错过它。我希望以下效果:
  1. 如果像上面那样明显地使用了错误的类型,请抛出警告或错误。
  2. 如果编译器无法可靠地检查我使用的类型是对还是错,请忽略它。
这是否可能?也许 mypy 可以做到,但我更喜欢使用 Python 3.6 风格的类型检查(例如 a: List[str]),而不是 mypy 中使用的注释风格(例如 # type List[str])。我很好奇是否有一个开关可以在本机 Python 3.6 中实现我所说的两点。

mypy已经支持Python 3.6的变量注释。 - user2357112
有关编程的内容,从英语翻译成中文。仅返回翻译后的文本:Python 3.5 中的类型提示是什么Python 3.6 中的变量注释是什么? - Dimitris Fasarakis Hilliard
简而言之,类型注释并不意味着类型强制。该语言仍然是动态类型的。 - jsbueno
5个回答

44
类型提示完全是为了被 Python 运行时忽略的,只有像 mypy 和 Pycharm 集成检查器这样的第三方工具才会进行检查。还有许多较不知名的第三方工具,可以使用类型注释在编译时或运行时进行类型检查,但大多数人使用 mypy 或 Pycharm 集成检查器。实际上,我甚至怀疑类型检查在可预见的未来内是否会被整合到 Python 中 - 参见 PEP 484(引入类型注释)和 PEP 526(引入变量注释)的“非目标”部分,以及 Guido 在此处的评论。个人而言,我希望类型检查能够更加紧密地集成到 Python 中,但似乎 Python 社区并没有准备好或愿意接受这种改变。
最新版本的mypy应该能够理解Python 3.6的变量注释语法和注释风格的语法。实际上,变量注释基本上是Guido的想法(Guido目前是mypy团队的一员) - 基本上,对于类型注释的支持在mypy和Python中几乎同时开发。

为了在运行时检查,我们(MIT CAVE团队)使用type_enforced(纯Python,无依赖项)。FYI-虽然有点自吹自擂,但仍然很有帮助。 - conmak

24
这可能吗?也许mypy可以做到,但我更愿意使用Python-3.6风格的类型检查(如 a: List[str]),而不是在mypy中使用注释风格(如 # type: List[str])。我很好奇在本地python 3.6中是否有开关可以实现我上面说的两点。
Python不会为您做到这一点;您可以使用mypy进行类型检查(并且PyCharm的内置检查器也应该可以)。除此之外,mypy还不会限制您仅使用类型注释# type List[str],您可以像在Python 3.6中那样使用变量注释,因此a: List[str]同样适用。
使用mypy需要安装typed_ast,并使用--fast-parser--python-version 3.6来执行mypy,正如mypy文档中所述。这可能很快就会改变,但现在您需要它们才能使其顺利运行。
更新:现在不再需要使用--fast-parser--python-version 3.6
完成后,mypy可以很好地检测到对a: List[str]的第二个操作的不兼容性。假设您的文件名为tp_check.py,其中包含以下语句:
from typing import List

a: List[str] = []
a.append('a')
a.append(1)
print(a)

使用上述参数运行mypy(您必须先pip install -U typed_ast):

python -m mypy --fast-parser --python-version 3.6 tp_check.py

捕获错误:

tp_check.py:5: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"

正如许多其他关于使用Python进行类型提示的答案所指出的那样,mypyPyCharm的类型检查器是执行验证的工具,而不是Python本身。目前Python并未使用此信息,它只将其存储为元数据,并在执行期间忽略它。


11
Python中的类型注解并不意味着强制类型。任何涉及运行时静态类型依赖关系的事情都意味着改变如此根本的变化,以至于继续称之为“Python”语言将毫无意义。
请注意,Python的动态特性允许我们构建一个外部工具,使用纯Python代码来执行运行时类型检查。这会使程序运行(非常)缓慢,但也许适用于某些测试类别。
确保 - Python语言的基本原则之一是:一切皆为对象,并且您可以尝试在运行时对对象执行任何操作。如果对象没有符合尝试操作的接口,则它将在运行时失败。
天生静态类型的语言以不同的方式工作:当尝试在运行时执行操作时,操作必须简单地在对象上可用。在编译步骤中,编译器在各个位置创建适当对象的空间和插槽 - 在不符合类型的情况下,会破坏编译。
Python的类型检查允许任何数量的工具做到这一点:在实际运行应用程序之前(但与编译本身无关)在步骤上进行中断和警告。但是,语言的本质不能被改变以实际要求对象在运行时遵守 - 并且在编译步骤中进行类型验证和破坏本身将是人为的。
虽然未来版本的Python可能会通过可选命令行开关将编译时类型检查纳入Python运行时本身,但这只是可以预期的。 (我认为它永远不会成为默认设置 - 至少不会破坏构建 - 也许可以使其默认用于发出警告)因此,在运行时,Python不需要静态类型检查,因为这将停止使用Python。但至少有一种语言同时使用动态对象和静态类型 - Cython语言,实际上作为Python超集。人们应该期望Cython很快将采用新的类型提示语法来进行实际的类型声明。(目前,它使用不同的语法来定义可选的静态类型变量)

0

pydantic 包提供了一个 validate_arguments 装饰器,可以在运行时检查类型提示。您可以将此装饰器添加到所有需要强制执行类型提示的函数或方法中。

我编写了一些代码来帮助自动化整个模块的这个过程,以便我可以启用运行时检查来帮助调试我的测试套件,但是对于使用该库的代码,则关闭它们以避免性能影响。

import sys
import inspect
import types
from pydantic import validate_arguments

class ConfigAllowArbitraryTypes:
    """allows for custom classes to be used in type hints"""
    
    arbitrary_types_allowed = True

def add_runtime_type_checks(module):
    """adds runtime typing checks to the given module/class"""

    if isinstance(module, str):
        module = sys.modules[module]
    
    for attr in module.__dict__:
        obj = getattr(module, attr)
        
        if isinstance(obj, types.FunctionType):
            setattr(module, attr, validate_arguments(obj, config=ConfigAllowArbitraryTypes))
        elif inspect.isclass(obj):
            # recursively add decorator to class methods
            add_runtime_type_checks(obj)

在我的测试套件中,我通过调用add_runtime_type_checks并提供模块名称来添加装饰器。
import mymodule

def setup_module(module):
    """executes once"""
    
    add_runtime_type_checks('mymodule')

def test_behavior():
   ...

请注意,使用pydantic进行类型检查时可能会进行一些意外的转换,因此如果期望的类型是int,而您传递了函数0.2,它将悄悄地将其转换为0而不是失败。原则上,您可以使用typen库的enforce_type_hints装饰器几乎做同样的事情,但它不进行递归检查(因此您不能使用像list[int]这样的类型,只能使用list)。

1
请注意,使用pydantic进行类型检查时可能会进行一些意外的转换,因此如果所需类型为int并且您将函数传递给0.2,则它将悄悄地将其转换为0而不是失败。您可以使用严格类型来强制指定类型。请参见:https://docs.pydantic.dev/usage/types/#strict-types - Cord Kaldemeyer

0
从Python3.7开始,您可以利用type_enforced模块。在Python3.9+中,type_enforced支持嵌套类型检查。
虽然它不能解决您的具体示例,但它支持对函数和方法进行类型检查。
举个例子:
pip install type_enforced

import type_enforced
from typing import List

@type_enforced.Enforcer
def my_fn(a:List[str]):
    a.append('a')

a=[]
b={}
my_fn(a)
my_fn(a)
print(a) # => ['a','a']
my_fn(b) # Raises error - see below

输出:

['a', 'a']
Traceback (most recent call last):
  File "/Users/conmak/development/personal/type_enforced/test.py", line 13, in <module>
    my_fn(b) # Raises error
    ^^^^^^^^
  File "/Users/conmak/development/personal/type_enforced/type_enforced/enforcer.py", line 171, in __call__
    self.__check_type__(assigned_vars.get(key), value, key)
  File "/Users/conmak/development/personal/type_enforced/type_enforced/enforcer.py", line 188, in __check_type__
    self.__exception__(
  File "/Users/conmak/development/personal/type_enforced/type_enforced/enforcer.py", line 129, in __exception__
    raise TypeError(f"({self.__fn__.__qualname__}): {message}")
TypeError: (my_fn): Type mismatch for typed variable `a`. Expected one of the following `[<class 'list'>]` but got `<class 'dict'>` instead.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接