【Code】初探Python类型标注

干点轻松的事情,看了一下Python官方文档里面关于类型标注的知识,怎么说,Python官方对于类型检查这个问题的认识,远远比那些Python吹成熟多了(好像说了一句废话,其实想表达的是按照现在Python的发展程度,过于灵活的类型系统早就成了发展的桎梏,官方的态度也表明了这一点,只是还有很多人没有意识到,还沉浸在不用写类型的快感中而已)。作为一个Python黑,对于Python建立健全的类型检查机制这件事上,我是举双手双脚赞成的。

从最基本出发

先来看看最基本的类型检查
def foo(x: int, y: int) -> str:
return f”{(x+y)=}”
限定了foo的参数类型必须为int,返回值为字符串类型。

在实际运行中,如果非要传入float或者其他实现了加法运算的类型,代码是可以正常运行的。只不过在PyCharm中,代码编写过程会提示参数错误的Warning。Mypy里面也会有提示,但我没有测试。

也就是说,类型检查目前(Python3.8版本)也只是起到了道德约束的作用。

Tuple

这时候很容易会想到,如果我函数返回多个值,能不能用类型检查呢?

当然可以,只需要引入Tuple类型

1
2
3
4
from typing import Tuple

def foo(x: int, y: int) -> Tuple[int, int]:
return 2x, 2y

Tuple[int, int]表示返回值是长度为2的元组类型,第一个元素和第二个元素都是int类型。

返回同类型多值的时候,比如Tuple[int, int, int]可以简写成Tuple[int, ...]

如果返回值类型不同,只需要写成Tuple[str, int, dict, float]这样,表示返回四个参数,类型分别是字符串,整数,字典和浮点数。

Union

这时候问题又来了,“有些时候我真的不确定能返回啥的嘛”

可能返回两种类型的时候(比如做JSON解析,最外层可以是dict或者Array),需要引入Union关键字

1
2
3
4
from typing import Union

def foo(filename: str) -> Union[dict, list]:
return json.load(open(filename))

Union[dict, list]即表示返回类型是字典或者列表类型(只能二选一)。

Optional

这时候又有钢筋拍案而起了,“我特么就喜欢给你返回None,空数据我就给你None,传入可以None,返回可以None,就看你咋整”

当然可以整,只需要引入Optional关键字

1
2
3
4
5
6
7
from typing import Optional

def foo(x: Optional[int]) -> Optional[str]:
if x is None:
return None
else:
return f"{x=}"

Optional[str]等价于Union[str, None],表示返回值为str类型或者None

Literal:不能枚举之痛

Python的枚举类型,用起来是很麻烦的,每次写Python的时候我都很怀念Swiftview.setColor(.red)之类的写法,省去了很多麻烦。

在Python里面很多喜欢用字符串来表示特定一些范围的东西,比如训练时候选用优化器,有把"Adam"或者"SGD"直接传入函数的操作。

问题就来了,字符串灵活性太高了,不开着官方文档根本不知道有哪些可以传入,有时候一个typo就会导致debug半天。

Literal就是为解决这个问题而来的

1
2
3
4
5
6
7
8
9
def validate_simple(data: Any) -> Literal[True]:  # always returns True
...

MODE = Literal['r', 'rb', 'w', 'wb']
def open_helper(file: str, mode: MODE) -> str:
...

open_helper('/some/path', 'r') # Passes type check
open_helper('/other/path', 'typo') # Error in type checker

它限定了参数只能是特定的范围中选择。完美解决上面的痛苦

啊不它根本不完美,我还是更喜欢Swift的枚举推断。

More Fun

仔细挖掘还可以看到有趣的东西。

函数名类型Callable[[int], str]表示(int)->str的函数。第一个位置为传入参数的类型和数值,第二个位置表示返回值类型。

不可变常量:Python也可以有Final。(Python3.8+)

1
2
3
4
5
6
7
8
MAX_SIZE: Final = 9000
MAX_SIZE += 1 # Error reported by type checker

class Connection:
TIMEOUT: Final[int] = 10

class FastConnector(Connection):
TIMEOUT = 1 # Error reported by type checker

类变量:静态语言中static干的事情,也被搬到了Python上面

1
2
3
class Starship:
stats: ClassVar[Dict[str, int]] = {} # class variable
damage: int = 10 # instance variable

调用的时候只能使用Starship.stats

Beyond

官方文档还有很多很有意思的,比如范型和函数重载等等,改天看完敲完代码再更新。从现在开始我写的每一行Python代码都会全力使用上面说到的所有新特性。

Python曾经困扰过我很长很长一段时间,从大三(2015)就开始学的语言,其实直到去年年初我还觉得我没有入门(可以翻历史记录,去年七月的时候还有对Python的吐槽),当中各种痛苦与挣扎,甚至有那么一瞬间会觉得如果博士不能毕业的话,Python肯定是最大的元凶,因为用它真的太痛苦了。直到后面追上新文档的改变之后才感觉打开新世界的大门,现在越用越顺手,甚至开始把平常常用的代码段整理成库发布到PyPI上面。

很庆幸当初没有因为厌恶和憎恨而逃避它,而是选择去了解它。

与此同时也庆幸是Python变成了我想要的样子,而不是我改变自己去适应以前的Python。

评论