Python输入模块–有效使用类型检查器

时间:2020-02-23 14:43:38  来源:igfitidea点击:

自从Python 3.5引入以来,Python的键入模块试图提供一种提示类型的方法,以帮助静态类型检查器和linter准确地预测错误。

由于Python必须在运行时确定对象的类型,因此开发人员有时很难找出代码中到底发生了什么。

即使像PyCharm IDE这样的外部类型检查器也无法产生最佳结果。
根据StackOverflow上的答案,平均而言,平均只有大约50%的时间可以正确预测错误。

Python尝试通过引入所谓的类型提示(类型注释)来缓解这种问题,以帮助外部类型检查器识别任何错误。
对于程序员来说,这是在编译时提示使用的对象类型的好方法,并确保类型检查器正常工作。

这也使Python代码对其他读者来说更具可读性和鲁棒性!

注意:这不会在编译时进行实际的类型检查。
如果返回的实际对象与提示的类型不同,则不会出现编译错误。
这就是为什么我们使用外部类型检查器(例如mypy)来识别任何类型错误的原因。

建议的前提条件

为了有效地使用"类型"模块,建议您使用外部类型检查器/衬垫来检查静态类型匹配。
用于Python的最广泛使用的类型检查器之一是mypy,因此,建议您在阅读本文的其余部分之前先安装它。

我们已经介绍了Python类型检查的基础。
您可以先阅读本文。

在本文中,我们将使用" mypy"作为静态类型检查器,可以通过以下方式安装它:

pip3 install mypy

您可以对任何Python文件运行" mypy"以检查类型是否匹配。
就像您正在"编译" Python代码一样。

mypy program.py

调试错误后,可以使用以下命令正常运行该程序:

python program.py

现在我们已经满足了前提条件,让我们尝试使用该模块的某些功能。

类型提示/类型注释

关于功能

我们可以注释一个函数以指定其返回类型和其参数的类型。

def print_list(a: list) -> None:
  print(a)

这通知类型检查器(在我的情况下为" mypy"),我们有一个函数" print_list()",该函数将" list"作为参数并返回" None"。

def print_list(a: list) -> None:
  print(a)

print_list([1, 2, 3])
print_list(1)

首先让我们在类型检查器" mypy"上运行此代码:

vijay@theitroad:~ $mypy printlist.py 
printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]"
Found 1 error in 1 file (checked 1 source file)

不出所料,我们得到一个错误;因为第5行的参数为" int",而不是" list"。

关于变量

从Python 3.6开始,我们还可以注释变量的类型,并提及类型。
但这不是强制性的,如果您希望在函数返回之前更改变量的类型。

# Annotates 'radius' to be a float
radius: float = 1.5

# We can annotate a variable without assigning a value!
sample: int

# Annotates 'area' to return a float
def area(r: float) -> float:
  return 3.1415 * r * r

print(area(radius))

# Print all annotations of the function using
# the '__annotations__' dictionary
print('Dictionary of Annotations for area():', area.__annotations__)

mypy的输出:

vijay@theitroad: ~ $mypy find_area.py && python find_area.py
Success: no issues found in 1 source file
7.068375
Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}

这是推荐的使用" mypy"的方法,首先使用类型注释,然后再使用类型检查器。

类型别名

"类型"模块为我们提供了类型别名,该类型别名是通过为别名分配类型来定义的。

from typing import List

# Vector is a list of float values
Vector = List[float]

def scale(scalar: float, vector: Vector) -> Vector:
  return [scalar * num for num in vector]

a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0])
print(a)

输出

vijay@theitroad: ~ $mypy vector_scale.py && python vector_scale.py
Success: no issues found in 1 source file
[2.0, 4.0, 6.0]

在上面的代码段中," Vector"是一个别名,代表浮点值的列表。
我们可以在别名上键入提示,这就是上面程序的作用。

此处提供可接受别名的完整列表。

让我们再看一个示例,该示例检查字典中每个key:value对,并检查它们是否与name:email格式匹配。

from typing import Dict
import re

# Create an alias called 'ContactDict'
ContactDict = Dict[str, str]

def check_if_valid(contacts: ContactDict) -> bool:
  for name, email in contacts.items():
      # Check if name and email are strings
      if (not isinstance(name, str)) or (not isinstance(email, str)):
          return False
      # Check for email [email protected]
      if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email):
          return False
  return True

print(check_if_valid({'vijay': '[email protected]'}))
print(check_if_valid({'vijay': '[email protected]', 123: '[email protected]'}))

mypy的输出

vijay@theitroad:~ $mypy validcontacts.py 
validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str"
Found 1 error in 1 file (checked 1 source file)

其中我们在mypy中得到一个静态编译时错误,因为第二个字典上的name参数是一个整数(123)。
因此,别名是从" mypy"强制执行准确类型检查的另一种方法。

使用NewType()创建用户定义的数据类型

我们可以使用NewType()函数来创建新的用户定义类型。

from typing import NewType

# Create a new user type called 'StudentID' that consists of
# an integer
StudentID = NewType('StudentID', int)
sample_id = StudentID(100)

静态类型检查器会将新类型视为原始类型的子类。
这有助于帮助捕获逻辑错误。

from typing import NewType

# Create a new user type called 'StudentID'
StudentID = NewType('StudentID', int)

def get_student_name(stud_id: StudentID) -> str:
  return str(input(f'Enter username for ID #{stud_id}:\n'))

stud_a = get_student_name(StudentID(100))
print(stud_a)

# This is incorrect!!
stud_b = get_student_name(-1)
print(stud_b)

mypy的输出

vijay@theitroad:~ $mypy studentnames.py  
studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID"
Found 1 error in 1 file (checked 1 source file)

任何类型

这是一种特殊类型,通知静态类型检查器(在我的情况下为" mypy"),每种类型都与此关键字兼容。

考虑我们以前的print_list()函数,现在可以接受任何类型的参数。

from typing import Any

def print_list(a: Any) -> None:
  print(a)

print_list([1, 2, 3])
print_list(1)

现在,当我们运行mypy时将没有任何错误。

vijay@theitroad:~ $mypy printlist.py && python printlist.py
Success: no issues found in 1 source file
[1, 2, 3]
1

没有返回类型或者参数类型的所有函数都将隐式默认使用Any

def foo(bar):
  return bar

# A static type checker will treat the above
# as having the same signature as:
def foo(bar: Any) -> Any:
  return bar

因此,您可以使用Any混合静态和动态类型的代码。