高天解析Python高级类型标注 NewType拦截同底层类型赋值错误

【python】Type Hint的进阶知识,这下总该有你没学过的内容了吧? — 码农高天

码农高天

普通类型别名无法阻止相同底层类型变量的错位赋值,引入NewType虽能在静态期杜绝逻辑隐患,但其强制显式包装要求也引发了代码背离Python原生风格的争议。

AI-assisted summary of 【python】Type Hint的进阶知识,这下总该有你没学过的内容了吧? — original source. Check the original for full context.

Source published:

Key ideas

  • 局部变量显式类型标注有助于 mypy 提前捕获空容器初始化后混入错误类型的隐患;显式标注 Any 比留空更有利于表达代码意图。 (0:00)
  • 函数无显式返回值时应标注为 None 而非 Any;对于直接抛出异常或退出程序的函数,应标注为 NoReturn,IDE 可据此自动识别并灰显后续死代码。 (0:00)
  • Callable 可通过方括号细化入参与返回值签名,配合 Literal 字面量类型能有效杜绝高阶函数传参错误与字符串拼写疏漏。 (5:00)
  • 普通类型别名无法防止具有相同底层类型(如 int)的变量相互错位赋值,而 NewType 可实现强类型派生隔离,但需要显式构造包装。 (10:00)

Detailed notes

## Background 该视频由 YouTube 频道“码农高天”于 2024 年 2 月 24 日发布。视频简介说明,本期内容基于上一期视频进一步探讨 Python 类型标注(Type Hint)的进阶用法,重点介绍一些相对常用但更不直观、日常容易忽略的类型检查知识点。

## TL;DR 视频系统性地讲解了 Python 类型系统中的进阶概念与实践技巧。首先厘清了局部变量类型标注的必要性,并严格区分了 Any、None 与 NoReturn 在函数返回值和控制流提示中的不同语义;接着深入介绍了高阶函数中 Callable 的参数与返回值签名约束,以及使用 Literal 限制合法取值范围的方法;最后对比了类型别名(Type Alias)与 NewType 的本质区别,探讨了如何通过 NewType 实现同底层类型的强类型安全,以及这种做法在类型安全与代码风格 Pythonic 之间的权衡。

## Atmosphere & style 整期视频呈现出清晰严谨、条理分明的技术布道风格,高天以沉稳且富有逻辑的语调娓娓道来。讲解紧扣实际开发中容易踩坑的边界场景,通过具体的代码正反例和 mypy 报错直观展现类型系统的运作逻辑。在探讨 NewType 的强类型约束时,他既指出了其防错价值,也客观指明了它对动态灵活风格的侵蚀,留给观众务实的权衡空间,整体氛围干货满满且具启发性。

## Bulletin - [00:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=0s) 局部变量显式类型标注有助于 mypy 提前捕获空容器初始化后混入错误类型的隐患;显式标注 Any 比留空更有利于表达代码意图。 - [00:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=0s) 函数无显式返回值时应标注为 None 而非 Any;对于直接抛出异常或退出程序的函数,应标注为 NoReturn,IDE 可据此自动识别并灰显后续死代码。 - [05:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=300s) Callable 可通过方括号细化入参与返回值签名,配合 Literal 字面量类型能有效杜绝高阶函数传参错误与字符串拼写疏漏。 - [10:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=600s) 普通类型别名无法防止具有相同底层类型(如 int)的变量相互错位赋值,而 NewType 可实现强类型派生隔离,但需要显式构造包装。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=0s) 类型标注不仅适用于函数入参和返回值,亦可直接用于局部变量,例如限制空列表 users 的内部元素类型。 - [00:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=0s) 显式声明好于隐式推导,暂时未想好类型或确实接受任意类型时显式标注 Any 比直接留空更清晰规范。 - [00:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=0s) 没有显式 return 的函数在运行时返回 None,若错误标注为 Any 将导致静态检查器无法拦截非法赋值。 - [00:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=0s) 直接抛出异常或调用 sys.exit() 的函数应标注为 NoReturn,IDE 能借助该标注将后续不可达代码标记为死代码。 - [05:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=300s) 编写高阶函数或装饰器时使用 Callable 标注参数,可静态拦截传入非可调用对象的调用错误。 - [05:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=300s) Callable 支持以 Callable[[Arg1Type, Arg2Type], ReturnType] 的形式精确约束入参列表及返回值的具体类型。 - [05:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=300s) Literal 用于限定变量的取值必须严格等于特定字面量,可有效预防类似性别枚举中的拼写错误。 - [05:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=300s) 在将字面量赋值给中间变量再传参时,mypy 默认将其推导为宽泛类型,需要对局部变量也进行 Literal 显式标注。 - [10:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=600s) 类型标注本身是 Python 对象,可以赋值给变量创建类型别名(Type Alias)以提高复杂签名的复用性与可读性。 - [10:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=600s) 普通类型别名仅是同义词,无法静态拦截两个底层类型相同(如 int)但业务语义不同的变量之间的错位赋值。 - [10:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=600s) NewType 能够在静态检查层面创建出独立派生类型,杜绝语义不同但底层相同类型的变量相互混用。 - [10:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=600s) 使用 NewType 声明的类型在运行时开销基本等同于空操作(no-op),但要求开发者显式调用构造方法进行包装转换。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=0s) 变量类型标注与函数的返回值标注(Any、None 与 NoReturn)

在实际编写 Python 代码时,类型标注(Type Hint)不仅适用于函数参数和返回值,也可以直接用于局部变量。码农高天举例说明:如果初始化一个空的列表 `users = []`,随后向其中追加元素,原本构想中 `users` 应该是一个存放用户名的字符串列表(`list[str]`),但 Python 默认允许向其中追加任意类型的对象(例如追加整型 `1`)。此时如果给变量加上显式类型标注 `users: list[str] = []`,静态类型检查工具 mypy 就会立刻捕获错误,指出追加整型 `1` 不符合 `users` 的类型定义。

紧接着,高天介绍了几个常用但容易混淆的类型标注。第一个是 `Any`,它代表“什么类型都有可能”。在不提供类型标注时系统默认按 `Any` 处理,但显式标注的意义重大:码农高天指出,“在大部分时候我们认为显式是要好于隐式的,所以无论你是觉得这个地方我现在还没有想好怎么标注,还是说我 take 的这个东西确实就是什么都行,标注一个 Any 都是比把它留空要更好的方法。”

针对函数的返回值标注,一个常见误区是“函数没有显式返回值所以它的返回类型是 Any”。高天明确纠正了该观点: - Python 中没有显式 `return` 的函数,在运行时实际上返回的是 `None`。如果标注为 `Any`,当外部执行 `i: int = f()` 时,mypy 无法报错,因为类型检查器认为 `f` 返回任何值都是合法的;但实际上该变量永远拿不到 `int`。当把 `f` 的返回值正确标注为 `None` 时,类型检查器就会准确拦截并提示错误。 - 另有一类函数是真的“不返回”——例如在执行过程中直接抛出异常(`raise Exception`)或直接退出了程序(如调用 `sys.exit()`)。对于这类函数,应该标注为 `typing.NoReturn`。标注后,IDE 能够自动识别该函数之后的代码为死代码(Dead Code),并将其置灰变暗以提示开发者该行代码永远不会被执行。

[05:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=300s) 可调用对象标注(Callable)与字面量类型(Literal)

在编写高阶函数或装饰器(Decorator)时,经常需要限制某个参数必须是一个可以被调用的对象,这就需要用到 `Callable`。如果仅写一个装饰器接受任意 `func`,从语法层面开发者可以给装饰器传入任意非可调用对象(如数字 `1`),运行期间才会崩溃。将参数标注为 `Callable` 后,静态检查就能直接拦截非法调用。

进一步地,`Callable` 还可以像 `list` 和 `dict` 一样使用方括号进行细化定义:`Callable[[Arg1Type, Arg2Type], ReturnType]`。方括号内包含两项内容,第一项是用方括号包裹的输入参数类型列表,第二项是返回值类型。例如定义一个用于 debug 的装饰器,要求被修饰的函数必须接收两个 `int` 并返回一个 `int`: ```mermaid flowchart LR accTitle: Callable 类型标注校验机制 accDescr: 展示 Callable 如何静态检查被修饰函数的参数列表与返回值类型。 subgraph CallableSpec["Callable 规范: Callable[[int, int], int]"] A["输入: [int, int]"] B["输出: int"] end subgraph Functions["待修饰函数"] F1["add(a: int, b: int) -> int"] -->|"匹配成功"| V["类型检查通过"] F2["abs(x: int) -> int"] -->|"参数数量不符"| E1["mypy 报错"] F3["add(a: str, b: str) -> str"] -->|"参数类型不符"| E2["mypy 报错"] end ``` 如果传入只有一个参数的绝对值函数,或者入参为 `str` 的函数,mypy 就会立即报错,指明两个 `Callable` 的签名不匹配,从而在静态期杜绝了运行时参数不符的问题。

随后,高天介绍了 `Literal`(字面量类型),其含义是参数的值“必须就得是这个样子的”。例如一个 `Person` 类中用字符串来表示性别 `gender`,但业务上只允许为 `'male'` 或 `'female'`。使用 `Literal['male', 'female']` 标注后,传入 `'woman'` 就会引发类型检查报错。这不仅能约束状态,还能防止开发者拼写错误。然而,如果开发者先声明变量 `g = 'female'` 再将其传入构造函数,mypy 仍会报错,因为系统默认推导 `g` 的类型是宽泛的 `str`;此时必须对局部变量也进行显式标注 `g: Literal['male', 'female'] = 'female'`。

[10:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=600s) 类型别名(Type Alias)与强类型包装(NewType)

针对重复书写长类型签名的问题,高天指出类型标注本身也是 Python 对象,可以赋值给变量作为类型别名(Type Alias)。例如直接声明 `GenderType = Literal['male', 'female']`,之后所有地方都可以复用 `GenderType`。不仅修改时只需改动一处,还能极大提升代码可读性: - 将普通类型重命名(如 `NameType = str`),能向阅读代码的人传达更多语义信息。 - 如果多个函数的返回值均为结构相同的元组(如 `tuple[int, Optional[str]]`,第一项为错误码,第二项为错误信息),给该元组起一个名字(例如 `ReturnType`),可以让函数签名更加直观清晰。

然而,单纯的类型别名只是同义词,类型检查器依然认为别名和原类型完全等价。高天用玩家数据类的例子阐述了其中的隐患:假设玩家类中有玩家 ID(`user_id`)和攻击力(`attack_point`),两者底层都是 `int`。即使定义了别名 `UserId = int` 和 `AttackPoint = int`,如果在更新方法中误将 `attack_point` 赋值给了 `user_id`,mypy 仍旧会判定通过,因为它们本质上都是 `int`,静态检查因此错失了捕获 Bug 的机会。

为了彻底解决这一问题,Python 引入了 `NewType`: - 使用 `UserId = NewType('UserId', int)` 和 `AttackPoint = NewType('AttackPoint', int)` 能够真正创建出独立的新类型。 - 此时若将 `AttackPoint` 赋值给 `UserId`,mypy 便会立即报错,提示两者类型不一致。 - 代价在于赋值时的灵活性降低:直接传入原始整数 `100` 会被判定类型不匹配,必须显式调用构造方法将其包裹(如 `UserId(1)` 与 `AttackPoint(100)`)。高天指出,虽然这两个包装函数在运行时(run time)基本等同于空操作(no-op),不会执行额外计算,但这种代码风格会引发争议:“这种方式当然让我们的程序变得不那么容易出错了,但是从另一个角度它也让 Python 变得更不 Python 了,就很多人到这个点的时候他就会认为不行这样实在是太多了,当然用与不用、怎么用、用多少,最终呢还是取决于你自己。”

- 类型别名与 NewType 的行为对比: | 特性 | 类型别名(Type Alias,如 `UserId = int`) | NewType(如 `UserId = NewType('UserId', int)`) | |:--- |:--- |:--- | | **类型等价性** | 与原类型完全等价,彼此可隐式混用 | 视为全新派生类型,与原类型及其他别名互不兼容 | | **交叉赋值检查** | 无法检测不同别名之间的错位赋值错误 | 能够严格静态拦截同底层类型的变量错位赋值 | | **初始化方式** | 直接传入原生字面量(如 `uid: UserId = 1`) | 必须显式包装转换(如 `uid = UserId(1)`) | | **运行时开销** | 无开销(纯静态别名) | 极低(运行时等效为恒等函数 no-op) |

## Notable quotes > [00:00](https://www.youtube.com/watch?v=6rgBwA7TRfE&t=0s) 在大部分时候我们认为显式是要好于隐式的,所以无论你是觉得这个地方我现在还没有想好怎么标注,还是说我 take 的这个东西确实就是什么都行,标注一个 Any 都是比把它留空要更好的方法。 — 码农高天

## Mentioned 码农高天, Python, mypy, typing

## Video description 这期视频我们讲一下type hint,也就是类型标注的进阶内容。在上一期视频的基础上,继续介绍一些相对也比较常用的用法。这次的知识点就没有上次那么直观了,一定会有你没学过的东西!

[Source](https://www.youtube.com/watch?v=6rgBwA7TRfE)

中文简报

高天解析Python高级类型标注 NewType拦截同底层类型赋值错误 普通类型别名无法阻止相同底层类型变量的错位赋值,引入NewType虽能在静态期杜绝逻辑隐患,但其强制显式包装要求也引发了代码背离Python原生风格的争议。 视频系统性地讲解了 Python 类型系统中的进阶概念与实践技巧。首先厘清了局部变量类型标注的必要性,并严格区分了 Any、None 与 NoReturn 在函数返回值和控制流提示中的不同语义;接着深入介绍了高阶函数中 Callable 的参数与返回值签名约束,以及使用 Literal 限制合法取值范围的方法;最后对比了类型别名(Type Alias)与 NewType 的本质区别,探讨了如何通过 NewType 实现同底层类型的强类型安全,以及这种做法在类型安全与代码风格 Pythonic 之间的权衡。 局部变量显式类型标注有助于 mypy 提前捕获空容器初始化后混入错误类型的隐患;显式标注 Any 比留空更有利于表达代码意图。 (0:00) 函数无显式返回值时应标注为 None 而非 Any;对于直接抛出异常或退出程序的函数,应标注为 NoReturn,IDE 可据此自动识别并灰显后续死代码。 (0:00) Callable 可通过方括号细化入参与返回值签名,配合 Literal 字面量类型能有效杜绝高阶函数传参错误与字符串拼写疏漏。 (5:00) 普通类型别名无法防止具有相同底层类型(如 int)的变量相互错位赋值,而 NewType 可实现强类型派生隔离,但需要显式构造包装。 (10:00)

Reading summary

码农高天系统介绍了 Python 进阶类型标注的使用技巧与设计权衡,指出合理运用高级类型系统可以在静态检查阶段拦截隐蔽的逻辑与运行时错误,但过度追求强类型也会对编码习惯带来约束。 变量显式标注与函数返回状态区分 (0:00) 类型标注不仅适用于函数签名,也可直接约束局部变量。针对初始化为空列表的情况,若仅声明 `users = []`,系统默认允许追加任意对象;显式指定 `users: list[str] = []` 后,静态类型检查工具 mypy 便能即时捕获错误追加整型等非法操作。关于通用类型 `Any`,码农高天强调显式优于隐式,无论开发者尚未确定类型还是该位置确实接受任意输入,显式写出 `Any` 均好过留空。 针对函数返回值,高天纠正了“无显式返回值即为 Any”的认知误区。没有显式 `return` 的函数运行时实际返回 `None`;若标注为 `Any`,外部将结果赋给其他类型(如 `int`)时 mypy 无法报警,而标注为 `None` 则能精准拦截。对于通过抛出异常或调用 `sys.exit()` 导致程序直接终止且不返回的函数,应使用 `typing.NoReturn` 进行标注,以便 IDE 将后续死代码自动置灰提醒。 可调用对象签名与字面量值的静态约束 (5:00) 编写装饰器或高阶函数时,若参数未标注类型,开发者可能传入数字等非可调用对象并在运行时崩溃。通过将其标注为 `Callable`,静态检查即可杜绝非法入参。此外,`Callable` 允许使用方括号对入参与返回值进行精准约束,例如 `Callable[[int, int], int]` 规定函数必须接收两个整型并返回一个整型,参数数量或类型不匹配的函数在修饰时都会被 mypy 拦截。 为进一步约束具体取值,`Literal`(字面量类型)可将变量或参数限定为特定固定值。例如将用户性别标注为 `Literal['male', 'female']`,既能防止传入其他非法字符串,也能在静态期规避拼写错误。需要注意的是,若先将字符串赋值给变量再传入,由于系统默认将其推断为宽泛的 `str`,开发者仍需在局部变量声明时显式赋予 `Literal` 标注。 类型别名与 NewType 的强类型权衡 (10:00) 长类型签名可以通过赋值操作定义为类型别名(Type Alias),如 `GenderType = Literal['male', 'female']`,这便于集中维护并提升元组或复杂签名的可读性。然而类型别名仅作为原类型的同义词,无法防范同底层类型间的错位赋值。例如玩家的 `user_id` 与 `attack_point` 均定义为 `int` 别名时,若在逻辑中互相误赋,mypy 仍会判定合法并漏判缺陷。 为解决此类隐患,Python 提供了 `NewType` 工具来派生出独立的新类型。使用 `NewType('UserId', int)` 创建类型后,错位赋值会被类型检查器直接拦截。但这种严格性也带来了编码限制:原生整数无法直接赋给新类型变量,必须使用类似 `UserId(1)` 的显式包装进行转换。码农高天指出,尽管此包装在运行时基本等同于空操作(no-op)而无性能惩罚,但这种强类型风格会让代码显得不那么符合传统 Python 的自由风格,具体如何取舍仍取决于开发者自身。 类型标注能够显著提升代码的鲁棒性与开发体验,但从纯粹的类型别名走向 `NewType` 等更严格的限制手段时,代码的灵活性也会相应下降,开发者需要在安全性与 Pythonic 的简洁风格之间自行权衡。

Source quotes

[0:00] 码农高天: 在大部分时候我们认为显式是要好于隐式的,所以无论你是觉得这个地方我现在还没有想好怎么标注,还是说我 take 的这个东西确实就是什么都行,标注一个 Any 都是比把它留空要更好的方法。

Original transcript

[0:00] 这期视频我们来继续介绍一些跟Typein有关的知识如果你没有看上一期视频的话我建议大家回去看一下上一期的入门视频那在上一期的视频中呢 我们举的例子都是给function 也就是给函数做typing的那在我们实际写程序的过程中我们不光可以给函数做Typing的也可以给变量做Typing的大家看我们左边这个例子 我有一个users是一个空的list然后往这个users里面append了一个string那在我的构想中可能这个users应该是一个list of string就是它每一个元素都应该是一个字符串的名字但是在现在这种情况下显然我是可以给users append任何东西的比如说我们把这个String A给变成一个Integer 1这个代码显然是没有任何问题的那这个时候如果我想显示的告诉程序说这个users是一个list of string我们就可以对这个variable本身进行类型标注我们看在对这个变量进行了类型标注之后这里买派就会报错了他告诉你你判的这个东西是1不符合这个users类型的定义那接下来呢我们讲几个之前没有提到的但是也比较常用的类型标注第一个要说的呢就是anyAnnie也很好理解 就是说这个东西是什么类型都有可能那实际上如果我们不进行变量标注它默认的就是any只不过在大部分时候我们认为显示是要好于隐示的所以无论你是觉得这个地方我现在还没有想好怎么标注还是说我take的这个东西确实就是什么都行标注一个any都是比把它留空要更好的方法那我们既然提到了any 就说一下这个函数返回值的标注我们刚才已经说了 如果你不标注函数返回值 它就会默认是any这个时候可能有人会认为我的函数没有返回值啊,所以它是any这个想法是错误的当你的函数没有返回值的时候,你的函数实际上是返回了难我们看左边我们定义的函数 它没有任何显示的return那这个函数一定是返回的Nan这里我们标注一个返回any 就是不理想的为什么呢我们看左边,我们让一个integi=flist这个时候买π不会给你报错的 因为他认为f返回任何东西都有可能但是我们明确的知道f是没有显示的返回的在python里没有显示的返回是等于返回n的也就是这个I永远不可能在F上面拿到一个Integer所以这个类型标注是有问题的我们看当我们把函数F的返回类型设置成NUN第七行就报错了他说这个F并不会返回一个值我们再验证一下刚才我们的说法当我们不对他进行类型标注的时候他会默认返回的是any这个时候就不报错所以说当大家的函数没有返回值的时候也不应该把它留空而是应该标注它返回那还有一些函数 他是真的不返回它不是正常的运行完这个函数之后没有显示的返回而返回NUN他可能是raise了一个exception或者他可能直接退出了程序那这个时候我们可以给它标注 no return我们看在把函数的返回值标注成no return之后这个IDE可以自动识别这个函数之后的deadcode这里第7行的A等于3它就暗下去了说明这行代码不会被运行接下来我们介绍一下callablecolourable的意思就是这个东西可以被当作一个函数一样调用比如说我们写一个非常简单的decorator那就像我们前面讲过的decorator这个东西呢可以直接当做函数对任何东西调用所以在下面当我做mydeck1的时候在语法上它是被允许的但是显然这是一个错误的调用对不对我们明显要求这个funk它是一个callable 也就是可以被调用的所以我们可以在这个mydeck上面标注这个funk当我们把这个funk标注成colourable的时候我们再做mydeck1 它就不好使了这里会显示这个东西它不是一个callable好 那我们看我们正常的使用一下这个Decorator把它放到我们写的一个add函数上他就不报错了那我们现在这个decorator功能呢只是在调用这个函数之前打一个start然后这个函数调用结束之后呢打一个end我想修改一下它的功能 [5:00] 我把他的功能变成了一个对于所有输入是两个integer返回是一个integer的函数打印他输入的两个argument和最后他得到的一个result你可以把它想象成一个小的debug工具对于加法来说我们只要加上decorator我们就可以看得清输入和返回值分别是什么你可以想象对于其他复杂的两个变一个的运算他都可以这么做但是这个时候就出现了一个问题如果我这个funk并不是这样两个输入一个返回或者说是两个int输入一个int返回的话我decorator可能在run time的时候就会出问题比如说 我写了一个绝对值函数它只有一个输入那这个时候呢我在run time的时候就会报错所以说在type hint的时候 我想定义这个funk也就是callable take什么样的argument 返回什么样的值我们看就像我们可以对list和dictionary做进一步的定义一样callable也是可以的callable里面的方括号我们要放两个内容第一个是他argument的type用方括号给扩起来第二个是它返回值的type那我现在这种写法代表的input是两个integer 它的返回值也是一个integer在这个胎片的下我们看第15行的这个decorator就报错了他会告诉你这两个colourable是不一样的那假如我们把这个add函数的a跟b都改成string同样他也会报错因为它也不满足输入的两个和返回值都是Integer这件事了所以说当我们要传一个函数的时候我们可以通过callable这个type hint去让程序静态的检查他的argument和他的返回值是不是匹配最后我们来介绍一下Literalliteral它的意思就是必须就得是这个样子的比如说我们有一个person类这个person类里面让你填一个gender那么Gender本身我们想用String的方式存储我们可能不想给他开一个1那这个时候我可以通过type hint 的方式规定这个 gender传进来的 string 只能是 male 或者是 female其他的都不可以那我们看在下面我们建立了两个person我们可以建立一个gender是male的爸爸但是我们不能建立一个gendered woman的爸爸因为我们并不是没有和female中的一个我们把这个woman变成female 它就work了那我们知道在python里string这个东西其实非常好用所以很多人会直接用String来表示有限的选择或者有限的状态那有时候写程序的时候呢可能会出现一些type就是你不小心打错了这个literal的type hint就可以帮你检查到这些问题但是你在使用它的时候呢 可能会发现一个问题这里我们看我们让G等于female然后在我们建立person的时候把g给传进gender里他就报错了他这里报错的原因是G这个东西他认为它是一个string而string显然是不符合这个literal male female的那我们为了告诉程序这个G保存的也是同样的literal我们可以把G这个变量进行一个类型标注就像刚才讲的一样我们看把G进行类型标注之后它就work了那这里就产生了一个问题如果我想把类似的literal给传来传去的我的代码就会变得很繁杂还有一个问题就是我们现在的gender只有两种对不对我们都知道随着社会的进步这个gender的种类会越来越多那当我们需要增加gender的时候是不是我们每一个type in的都要进行修改呢这显然不太符合我们对编程的直觉那在python里面这个type hint本身也是一个python object它是可以被复制到一个变量上的也就是说我们可以这么写我们看,我们可以直接让Gender type等于Literal Male Female然后剩下所有的type pin的我们都可以直接用这个type了当我们需要修改这个liter的时候我们只需要在一个地方完成修改就够了那不仅如此 给这个类型增加一个变量名 还会增加我们的代码可读性举个例子当我们把string重新命名为name type [10:00] 10号我们再看到这个内容输入的type它就会更加清晰它带给我们人类的信息是更多的如果上面那个例子还不够明显的话我们来看现在这个例子假设我写了很多函数 这些函数有个特点 就是它所有的返回值都是一个tuple第一个值代表return code也就是这个函数的运行是不是成功了如果没有成功的话 它的Error Code是什么第二个值呢是他的Error Message成功的时候是难不成空的时候是一个string用来描述他为什么没有成功那如果我们要标注这个函数的返回值呢就是tuple 翻过号 int 逗号 optional string但是对于第一次读你代码的人来说这个东西他不知道你要干什么或者说可能有其他地方返回的是同样的type 但是意思完全不同这样就会让人有点懵如果我们把它重新命名成一个return type或者是一些其他你觉得更有意义的名字它在这个函数里就显得更加的明确了但是呢 像这种把类型直接换一个名字的方式 有一些坏处坏出就是他会认为这两个东西是完全等价一模一样的我们来看下面的例子我们有一个非常简单的玩家的class然后在initialization的时候呢他的uid 玩家的id和他的attack 攻击值都是 integer为了区别它们呢,我们给int做了两个alias,一个叫userid,一个叫attackpoint但是在我写的这个update attack这个method里面我不小心写错了我把这个attack point给复制给UID了可现在由于我只是给int重新起了两个名字他就会认为 attack 和 uid 是一样的东西 他们都是 int 没有任何问题这个时候一个本来我们有机会在静态检查的时候发现的bug就被我们错过去了那为了解决这个问题呢Python引入了一个新的东西叫做NewType我们看所谓new type就是我们真正建立一个新的type然后这个type的实际类型是什么什么比如说在第二行我们就建立了一个名字叫 user id 的新 type然后这个type本质是int在做了这个操作之后 我们看第15行的时候 当我们尝试把这个attack point的type复制给一个user id的type的时候 它就报错了它这里就显示这两个变量的类型不一样 因为attack point和user id已经被python认为是两个不同的类型了当然这种用法也会产生一个问题就是 我们没有办法再用int给它复制了因为他认为user id跟int也不是同一个类型我们看第17行我们尝试做一个player100它两个地方都报错因为我们给进去的变量是一个int那这个时候对应的处理方式呢就是把这两个 int变成我们定义的type我们看我们需要显示的把这个1变成一个user id 把这个100变成一个attack point我们的程序才能正常的运行当然在run time的时候呢这两个操作基本就是一个no up他实际上是不会做任何事情的这种方式当然让我们的程序变得不那么容易出错了但是从另一个角度它也让python变得更不python了就很多人到这个点的时候他就会认为不行这样实在是太多了当然用与不用怎么用用多少最终呢还是取决于你自己好 那这期TypeIn的进阶视频就到这里 希望大家有所帮助 喜欢的话记得点一下关注 别忘了意见三连哦优优独播剧场——YoYo Television Series Exclusive