typing库:让你的代码阅读者再也不用猜猜猜
Python以其简洁的代码而闻名于世。除了缩进之外,代码样式和文档主要取决于编写应用程序的开发人员的习惯,这可能导致一些混乱,产生难以理解的代码。而这主要是因为Python是一种动态类型语言,请看以下代码。在Python中,这是完全可以接受的。
age = 21
print(age) # 21
age = 'Twenty One'
print(age) # Twenty One
在上面的代码中,age的值首先是一个int,但之后我们将它更改为str。每个变量都可以在程序中任何一点的表示任何值。这就是动态类型的力量!
让我们尝试用静态类型语言(如Java)做同样的事情。
int age = 21;
System.out.print(age);
age = "Twenty One";
System.out.print(age);
我们实际上最终得到以下错误,因为我们试图将“Twenty One”(一个字符串)分配给声明为int的变量age。
Error: incompatible types: String cannot be converted to int
为了使用静态类型语言,我们必须使用两个单独的变量。
int ageNum = 21;
System.out.print(ageNum);
String ageStr = ageNum.toString();
System.out.print(ageStr);
这很有效,但我更喜欢Python这种动态语言的灵活性,这不会使得我的代码更加复杂混乱。此外,我也喜欢静态类型语言的可读性,以便其他程序员知道特定变量应该是什么类型!为了充分利用这两个方面,Python 3.5引入了类型注释
(Type Annotations)。
什么是类型注释?
类型注释是PEP 484
中添加的新功能,允许向变量添加类型提示。 它们用于提醒代码阅读者知晓此处的变量应该什么类型。 这为动态类型的Python带来了静态类型的感觉。 这是通过在初始化/声明变量后添加:<type>
来实现的。
下面显示了一个示例,当我们声明变量以显示age应该是int类型时,它会添加:int
。
age: int = 5
print(age)
5
值得注意的是,类型注释不会以任何方式影响程序的运行时
。 解释器会忽略这些提示,仅用于提高其他程序员和您自己的可读性。
为什么以及如何使用类型注释?
静态类型语言的一个很有用特点是在代码中特定作用域内的变量的值的类型总是明确的。 例如,我们知道字符串变量只能是字符串,整数只能是整数,依此类推。 而使用动态类型语言,任何人对于代码中的变量都不是百分百明确或者立刻明确,都是需要理解加猜测才能知晓变量的类型。
在编写和调用函数时,我们可以使用预期的变量类型,以确保我们正确地传递和使用参数。 如果我们的函数期望传入int时反而传入str,那么它很可能不会按照我们预期的方式工作。
请考虑以下代码:
def mystery_combine(a, b, times):
return (a + b) * times
我们可以看到该函数正在做什么,但我们知道a,b或times应该是什么吗? 查看下面的代码,特别是在我们用不同类型的参数调用mystery_combine的两行中。 观察每个版本的输出,该输出显示在每个块下面的注释中。
def mystery_combine(a, b, times):
return (a + b) * times
print(mystery_combine(2, 3, 4))
# 20
print(mystery_combine('Hello ', 'World! ', 4))
# Hello World! Hello World! Hello World! Hello World!
根据上面两个调用函数的例子,我们得到两个完全不同的结果。 将整数
传入函数我们得到数学
,但是当我们将字符串
传递给函数时,我们可以看到前两个参数是拼接字符串
,并且结果字符串乘以倍数
。
事实证明,编写该函数的开发人员实际上预期的运行效果应该是拼接并乘以倍数
,使用类型注释,我们可以清除这种混淆。
def mystery_combine(a: str, b: str, times: int) -> str:
return (a + b) * times
我们在函数的参数中添加了:str
,:str
和:int
来显示它们应该是什么类型。 这有望使代码更清晰,并且能让代码阅读者快速理解函数的目的。
我们还添加了 -> str
来表明这个函数将返回一个str。 使用 -> <type>
,我们可以更轻松地显示任何函数或方法的返回值类型
,以避免未来开发人员的混淆!
同样,我们仍然可以以第一种不正确的方式调用我们的代码,但希望通过良好的审查,程序员将看到他们正在以一种非预期的方式使用该函数。 类型注释和提示对团队和多开发人员Python应用程序非常有用。 它消除了阅读代码过程中大部分猜测,节约了码农的时间!
复杂类型Complex Type
上一节处理了许多类型注释的基本用例,但都是很基础的例子,接下来让我们分解一些更复杂的例子。
对于Python语言的typeing库。 可以描述任何变量的任何类型(类型注释)。 它预装了多种类型注释,如Dict,Tuple,List,Set等等! 然后,您可以将类型提示扩展为用例,如下例所示。
from typing import List
def print_names(names: List[str]) -> None:
for student in names:
print(student)
这将告诉代码阅读者names参数应该是字符串列表,返回None。
字典也以类似的方式工作。
from typing import Dict
def print_name_and_grade(grades: Dict[str, float]) -> None:
for student, grade in grades.items():
print(student, grade)
Dict[str,float]
类型提示告诉我们,grades参数传入的应该是一个字典,其中键是字符串,值是浮点数。 其他复杂的示例依然可以使用typing模块。
起个别名
如果要使用自定义类型名称,可以使用类型别名(Type Aliases)
。假设您正在使用一组[x,y]点
作为元组,那么我们可以使用别名将Tuple类型
映射到Point类型
。
from typing import List, Tuple
# 声明Point类型,该Point类实际上是[x, y]元组的别名
Point = Tuple[int, int]
# points参数是很多个Point类组成的列表
def print_points(points: List[Point]):
for point in points:
print("X:", point[0], " Y:", point[1])
返回多个值
如果你的函数以元组类型返回多个值,那么只需将预期输出包装为输入Tuple [<type 1>,<type 2>,...]
from typing import Tuple
def get_api_response() -> Tuple[int, int]:
successes, errors = ... # Some API call
return successes, errors
上面的函数get_api_response
代码返回API调用中成功
和错误数量
的元组,其中两个值都是整数。 通过使用Tuple [int,int]
,我们向开发人员指出这个函数确实返回了多个int值。
多种可选类型
如果某个函数的参数可以是多种可选类型,则可以使用typing.Optional
或typing.Union
类型。
如果该参数可以是 特定类型 ,也可以是 None ,则使用 Optional 。
from typing import Optional
def try_to_print(some_num: Optional[int]):
if some_num:
print(some_num)
else:
print('Value was None!')
上面的函数try_to_print函数
中的some_num参数
可以是int
也可以是None
当参数采用 更具体的类型提示 时使用 Union。
from typing import Union
def print_grade(grade: Union[int, str]):
if isinstance(grade, str):
print(grade + ' percent')
else:
print(str(grade) + '%')
上面的代码表明grade
可以是int或str类型。 这对我们打印grade
的示例很有帮助,因此我们可以打印98%或98%,没有其他意外的情况。
更多例子
有关更多示例,请查看typing模块的官方Python文档。
https://docs.python.org/3/library/typing.html
他们有很多不同的例子,你可以查看。 这篇文章仅仅是冰山一角,但希望本文的简单介绍能让大家设计的代码因为有类型注释而变得更加清晰,更易阅读。