众所周知,Python 是动态类型语言,运行时不需要指定变量类型。这一点是不会改变的,但是2015年9月创始人 Guido van Rossum 在 Python 3.5 引入了一个类型系统,允许开发者指定变量类型–类型提示(Type Hints)。它的主要作用是方便开发,供IDE 和各种开发工具使用,对代码运行不产生影响,运行时会过滤类型信息。
主要优点如下:
1、易于理解代码
指定函数输入和输出,便于理解代码片段的过程。永远记住你阅读的代码,比你写的代码要多得多。
有了类型提示(Type Hints),在调用函数时就可以告诉你需要传递哪些参数类型;以及需要扩展/修改函数时,也会告诉你输入和输出所需要的数据类型。 例如,想象一下以下这个发送请求的函数,
def send_request(request_data : Any,
headers: Optional[Dict[str, str]],
user_id: Optional[UserId] = None,
as_json: bool = True):
...
只看这个函数签名,我们就可以知道:
- request_data可以是任何数据
- header的内容是一个可选的字符串字典
- UserId是可选的(默认为None),或者是符合编码UserId的任何数据
- as_json需要始终是一个布尔值(本质上是一个flag,即使名称可能没有提供这种提示)
而类型提示系统可以将类型信息从文档中移动到更加接近函数的接口,然后以一个明确定义的方式来声明复杂的类型要求。同时,构建linters,并在每次更改代码后运行它们,可以检查这些类型提示约束,确保它们永远不会过时。
2、 易于重构
类型提示可以在重构时,更好得帮助我们定位类的位置。
虽然许多IDE现在采用一些启发式方法提供了这项功能,但是类型提示可以使IDE具有100%的检测准确率,并定位到类的位置。这样可以更平滑,更准确地检测变量类型在代码中的运行方式。
请记住,虽然动态类型意味着任何变量都可以成为任何类型,但是所有变量在所有时间中都应只有一种类型。类型系统仍然是编程的核心组件,想想那些使用isinstance判断变量类型、应用逻辑所浪费的时间吧。
3、 易于使用库
使用类型提示意味着IDE可以拥有更准确、更智能的建议引擎。当调用自动完成时,IDE会完全放心地知道对象上有哪些方法/属性可用。此外,如果用户尝试调用不存在的内容或传递不正确类型的参数,IDE可以立即警告它。
4、验证运行数据
类型标注(Type annotations)是一种直接的方式,并且是类型文档中最常见到的那种方式。
使用:语句将信息附加到变量或函数参数中。,
->运算符用于将信息附加到函数/方法的返回值中。
好处:
这是实现类型提示的规范方式,这意味着是类型提示中最干净的一种方式。
因为类型信息附加在代码的右侧,这样我们可以立刻明晰类型。
缺点:
它不向后兼容。至少需要Python 3.6才能使用它。
强制你导入所有类型依赖项,即使它们根本不在运行时使用。
在类型提示中,会使用到复合类型,例如List[int]。而为了构造这些复杂类型,解释器在首次加载此文件时需要执行一些操作。
类型标注的使用
类型标注(Type annotations)是一种直接的方式,并且是类型文档中最常见到的那种方式。
声明一个函数参数的类型,只要在参数名称的后面加个":“号,带上类型名称就行了。声明函数的返回值类型,只要在函数声明结束之前,也就是”:“号之前加入一个”->",带上类型名称。
常见数据类型
- int,long,float: 整型,长整形,浮点型
- bool,str: 布尔型,字符串类型
- List, Tuple, Dict, Set:列表,元组,字典, 集合
- Iterable,Iterator:可迭代类型,迭代器类型
- Generator:生成器类型
1) 基本数据类型
def test(a:int, b:str) -> str:
print(a, b)
return 1000
if __name__ == '__main__':
test('test', 'abc')
函数test,a:int 指定了输入参数a为int类型,b:str b为str类型,-> str 返回值为srt类型。
可以看到,在方法中,我们最终返回了一个int,此时pycharm就会有警告;
当我们在调用这个方法时,参数a我们输入的是字符串,此时也会有警告;
但非常重要的一点是,pycharm只是提出了警告,但实际上运行是不会报错,毕竟python的本质还是动态语言
2)复杂的类型标注
示例1
from typing import List
Vector = List[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
# typechecks; a list of floats qualifies as a Vector.
new_vector = scale(2.0, [1.0, -4.2, 5.4])
示例2
from typing import Dict, Tuple, Sequence
ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]
def broadcast_message(message: str, servers: Sequence[Server]) -> None:
...
# The static type checker will treat the previous type signature as
# being exactly equivalent to this one.
def broadcast_message(
message: str,
servers: Sequence[Tuple[Tuple[str, int], Dict[str, str]]]) -> None:
...
):
...
这里需要注意,元组这个类型是比较特殊的,因为它是不可变的。
所以,当我们指定Tuple[str, str]时,就只能传入长度为2,并且元组中的所有元素都是str类型
3) 泛型指定
from typing import Sequence, TypeVar, Union
T = TypeVar('T') # Declare type variable
def first(l: Sequence[T]) -> T: # Generic function
return l[0]
T = TypeVar('T') # Can be anything
A = TypeVar('A', str, bytes) # Must be str or bytes
A = Union[str, None] # Must be str or None
创建变量时类型指定
from typing import NamedTuple
class Employee(NamedTuple):
name: str
id: int = 3
employee = Employee('Guido')
assert employee.id == 3
4) 参数注释
def send_mail(sender: "fish@example.com",
receiver: "panda@example.com",
subject: "say hello to you.",
message: "hello.",
attachments: list("type<io.BytesIO>")
) -> bool:
pass
创建变量时类型指定
写在":“号后面的并一定是一个类型。Python把这种写法称为"annotations”(标注),在运行的时候完全不使用它。它是专门设计出来给程序员和自动处理程序看的。任何可被计算出来的东西都可以写在那里。
很明显,与直接写类型相比,直接把参数是什么样子写出来更容易让调用者看清楚函数的使用方法。更激进的话,标注还可以是被计算出来的。比如下面这个例子:
def add_matrix3x3(x: [(1, 1, 1), (1, 1, 1), (1, 1, 1)],
y: [(2, 2, 2), (2, 2, 2), (2, 2, 2)],
) ->[(3, 3, 3), (3, 3, 3), (3, 3, 3)]:
pass
*不足之处
示例
from typing import List
def test(b: List[int]) -> str:
print(b)
return 'test'
if __name__ == '__main__':
test([1, 'a'])
从这个例子可以看出来,虽然我们指定了List[int]即由int组成的列表,但是,实际中,只要这个列表中存在int(其他的可以为任何类型),就不会出现警告
参考文章
1、https://sikasjc.github.io/2018/07/14/type-hint-in-python/
2、https://docs.python.org/zh-cn/3/library/typing.html
3、https://hgoldfish.com/blogs/article/83/