相信有很多同学在写Python的时候,会使用类型标注来提高代码的可读性,同时还能帮助IDE实现自动补全。
假设我们现在获得了一个对象,这个对象可能是列表也可能是生成器,我写一个函数,获取它的第一个元素。代码很简单:
from typing import Iterator from contextlib import suppress class People: def __init__(self, name): self.name = name def eat(self): print(f'{self.name}正在吃饭') def walk(self): print(f'{self.name}正在走路') def get_first_element(ele_list): if isinstance(ele_list, list): return ele_list[0] if ele_list else None if isinstance(ele_list, Iterator): with suppress(Exception): value = next(ele_list) return value return None if __name__ == '__main__': kingname = People('kingname') pm = People('pm') people_list = [kingname, pm] obj = get_first_element(people_list) if obj: print(obj.)
代码写好了,但是当我获取第一个元素,想打印它里面的数据的时候,我发现我忘记了People这个类有哪些属性了,而此时PyCharm的自动补全也失效了,我不得不把代码往回翻,去寻找People定义的位置,效率非常低。如下图所示。
如果我们使用了类型标注,就能解决这个问题:
这个常规用法,大家肯定都知道。
现在问题来了,我们除了People
类,还有Cat
类,并且列表里面的元素可能全是People
类的实例,也可能全是Cat
类实例,这种情况怎么办呢?
首先你遇到了第一个问题,get_first_element
的参数的类型标注怎么写?
你可能会写成这样:
def get_first_element(ele_list: Union[List[Union[People, Cat]], Iterator[Union[People, Cat]]])
那如果还有一个Dog
类呢?
为了简化操作,你可能会用Any
,类型,于是get_first_element
变成了下面这样:
def get_first_element(ele_list: Union[List[Any], Iterator[Any]]) -> Optional[Any]: if isinstance(ele_list, list): return ele_list[0] if ele_list else None if isinstance(ele_list, Iterator): with suppress(Exception): value = next(ele_list) return value return None
现在你发现问题又来了,PyCharm的自动补全又坏了。因为Any是任何类型,所以在代码运行前,它其实不知道你返回的是什么东西。如下图所示:
这种情况下,你就需要使用Python类型标注中的泛型
了。我们知道,泛型是静态语言中的概念,Python由于使用了类型标注,也有了类型。于是也就借用了这个概念。
我们来看看怎么使用它:
from typing import TypeVar T = TypeVar('T')
注意这里的变量名T
和TypeVar的参数'T'
可以同时写成任意字符串,但变量名要与参数保持一致。例如:
GodType = TypeVar('GodType')
然后把T当作Any
一样使用就可以。我们来看看效果:
可以看到,PyCharm又能自动补全了。使用TypeVar
,可以告诉PyCharm,返回的类型跟传入参数中的T
对应位置的类型保持一致。例如传入参数中,T
在List[T]
或者
Generator[T]
中,所以返回的参数需要与列表中的元素或者生成器中的元素类型保持一致。
我们用Cat生成器来测试一下,发现也能自动补全:
还有更厉害的,如果我的列表里面既有Cat
的实例,又有People
的实例怎么办?这个时候,PyCharm会直接把两个实例的可能补全都给你列出来:
请关注微信公众号【未闻Code】获取更多精彩文章。