关于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'
问题是,fallback_name不是一个字符串 - 它是一个字典。所以在fallback_name上调用get_first_name可能会非常糟糕,因为它没有.split()函数。 这是一个简单而明显的错误修复,但是这个错误隐藏的是,直到用户运行程序并将该名称留空为止,您永远不会知道该错误。你可以自己测试程序一千次,注意不到这个简单的错误,因为你总是输入一个名字。
静态类型就会防止这种错误。甚至在尝试运行该程序之前,静态类型会告诉您,您不能将fallback_name传递到get_first_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,它会自动检查类型,并在您打到“运行”之前显示错误的地方:
更多Python 3.6静态类型语法示例
声明str或int变量很简单。当您使用更复杂的数据类型(如嵌套列表和字典)时,令人头痛的事就会发生。幸运的是,Python 3.6的新语法不是太糟糕 - 至少不是事后添加类型的语言。
基本模式是从中导入复杂数据类型的名称typing
模块,然后在括号中传入嵌套类型。
您将使用的最常见的复杂数据类型是Dict
,List
和Tuple
,以下是使用它们的方式:
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本身在运行时不会对这些类型做任何事情,所以您不能通过添加类型声明来意外地破坏程序。