如何在Python3.6中使用静态检查

发布于 2017-07-04 03:34:42

原文链接

关于Python语言的最常见的抱怨之一是变量是动态类型化的。这意味着您声明变量而不给出特定的数据类型。根据传递的数据自动分配类型:

president_name = "Franklin Delano Roosevelt"
print(type(president_name))

# Result is:
# <class 'str'>

在这种情况下,变量president_name由于我们传入一个字符串而被创建为str类型。但Python不知道它将是一个字符串,直到它实际上运行这行代码:

String president_name = "Franklin Delano Roosevelt";

因为Java提前知道president_name只能持有一个String,它会给你一个编译错误,如果你试图做一些愚蠢的东西,像存储一个整数,或传递给一个函数,期望不是一个字符串。

为什么要关心类型?

以Python的动态类型语言编写新代码通常更快,因为您不需要手动写出所有类型的声明。但是,当您的代码库开始变大时,您将不可避免地遇到静态类型引起的许多运行时错误。

以下是Python中难以置信的常见错误的示例:

def get_first_name(full_name):
    return full_name.split(" ")[0]

fallback_name = {
    "first_name": "UserFirstName",
    "last_name": "UserLastName"
}

raw_name = input("Please enter your name: ")
first_name = get_first_name(raw_name)

# If the user didn't type anything in, use the fallback name
if not first_name:
    first_name = get_first_name(fallback_name)

print(f"Hi, {first_name}!")

我们正在做的是询问用户的姓名,然后打印出“Hi,<first_name>!”。如果用户没有输入任何内容,我们想打印出“Hi,UserFirstName!”作为后备。
如果您运行该程序并输入名称,该程序将会运行正常,但如果将该名称留空,则该程序会崩溃:

Traceback (most recent call last):
  File "test.py", line 14, in <module>
    first_name = get_first_name(fallback_name)
  File "test.py", line 2, in get_first_name
    return full_name.split(" ")[0]
AttributeError: 'dict' object has no attribute 'split'

问题是,fallbackname不是一个字符串 - 它是一个字典。所以在fallbackname上调用getfirstname可能会非常糟糕,因为它没有.split()函数。 这是一个简单而明显的错误修复,但是这个错误隐藏的是,直到用户运行程序并将该名称留空为止,您永远不会知道该错误。你可以自己测试程序一千次,注意不到这个简单的错误,因为你总是输入一个名字。

静态类型就会防止这种错误。甚至在尝试运行该程序之前,静态类型会告诉您,您不能将fallbackname传递到getfirst_name()中,因为它会期望一个str,但是您要给它一个Dict。您的代码编辑器甚至可以突出显示您输入的错误!

好消息是,您现在可以在Python中使用静态类型。而对于Python 3.6,终于有一种人性化的语法来声明类型。

修复我们的buggy程序

我们通过声明每个变量的类型和每个函数的输入/输出来更新buggy程序。以下是更新版本:

from typing import Dict

def get_first_name(full_name: str) -> str:
    return full_name.split(" ")[0]

fallback_name: Dict[str, str] = {
    "first_name": "UserFirstName",
    "last_name": "UserLastName"
}

raw_name: str = input("Please enter your name: ")
first_name: str = get_first_name(raw_name)

# If the user didn't type anything in, use the fallback name
if not first_name:
    first_name = get_first_name(fallback_name)

print(f"Hi, {first_name}!")

在Python 3.6中,你声明一个这样的变量类型:

variable_name: type

如果您在创建变量时分配初始值,那么这么简单:

my_string: str = "My String Value"

声明一个函数的输入和输出类型,如下所示:

def function_name(parameter1: type) -> return_type:

这很简单 - 只是对普通Python语法的一个小小的调整。但是现在,类型被声明,看看当我运行类型检查器会发生什么:

$ mypy typing_test.py

test.py:16: error: Argument 1 to "get_first_name" has incompatible type Dict[str, str]; expected "str"

没有执行该程序,它知道第16行没有办法工作!您可以立即修复错误,而无需等待用户在三个月后才能发现。 如果您正在使用像PyCharm这样的IDE,它会自动检查类型,并在您打到“运行”之前显示错误的地方:

1

更多Python 3.6静态类型语法示例

声明str或int变量很简单。当您使用更复杂的数据类型(如嵌套列表和字典)时,令人头痛的事就会发生。幸运的是,Python 3.6的新语法不是太糟糕 - 至少不是事后添加类型的语言。

基本模式是从中导入复杂数据类型的名称typing模块,然后在括号中传入嵌套类型。

您将使用的最常见的复杂数据类型是DictListTuple,以下是使用它们的方式:

from typing import Dict, List

# A dictionary where the keys are strings and the values are ints

name_counts: Dict[str, int] = {
    "Adam": 10,
    "Guido": 12
}

# A list of integers

numbers: List[int] = [1, 2, 3, 4, 5, 6]

# A list that holds dicts that each hold a string key / int value

list_of_dicts: List[Dict[str, int]] = [
    {"key1": 1},
    {"key2": 2}
]

元组有点特别,因为它们可以分别声明每个元素的类型:

from typing import Tuple

my_data: Tuple[str, int, float] = ("Adam", 10, 5.7)

您还可以通过将其分配给新名称,为复杂类型创建别名:

from typing import List, Tuple

LatLngVector = List[Tuple[float, float]]

points: LatLngVector = [
    (25.91375, -60.15503),
    (-11.01983, -166.48477),
    (-11.01983, -166.48477)
]

有时您的Python函数可能足够灵活,可以处理几种不同的类型或任何数据类型。你可以使用Union类型以声明可以接受多种类型的功能,并可以使用Any接受任何东西。 Python 3.6还支持您可能在其他编程语言(如通用类型和自定义用户定义类型)中看到的一些奇特的静态类型。

运行类型检查器

虽然Python 3.6为您提供了声明类型的语法,但Python本身绝对没有任何东西,但是对这些类型声明也是这样。要实际执行类型检查,您需要执行以下两项操作之一:

1.下载开源的mypy类型检查器,并将其作为单元测试或开发工作流程的一部分运行。

2.使用IDE中内置类型检查的PyCharm。或者如果您使用Atom等其他编辑器,请下载它自己的类型检查插件。

我建议两个都做。PyCharm和mypy使用不同类型的检查实现,并且它们可以分别捕获另一个不执行的事物。您可以使用PyCharm进行实时类型检查,然后运行mypy作为单元测试的一部分,作为最终验证。

太好了!我应该开始用类型声明编写所有的Python代码吗?

这种类型的声明语法对于Python来说是非常新鲜的 - 它仅仅适用于Python 3.6。如果您将类型化的Python代码显示给另一个Python开发人员,那么很有可能他们会认为你很疯狂,甚至不相信语法是有效的。而mypy型检查器仍在开发中,并不声称稳定。

所以,对于所有的项目来说,整个猪都可能要早一点。但是,如果您正在开发一个新项目,您可以将Python 3.6作为最低要求,这可能值得尝试静态类型,因为它可以防止大量的错误。

其中一个简单的事情是,您可以轻松地将动态类型与静态声明进行混合。因此,您可以先在最有价值的地方声明类型,而无需更改所有代码。而且由于Python本身在运行时不会对这些类型做任何事情,所以您不能通过添加类型声明来意外地破坏程序。