写出更现代化的Python代码:聊聊 Type Hint

news2025/1/13 3:09:57

Type Hint是 Python 3.5 新增的支持,中文可以译为 类型提示。屏幕前的你或许听过,又或许没有。所以今天,让我们一起了解了解。
本文基于 Python 3.10.4,部分代码需要在 Python 3.10.0 及以上运行,原因在后续文章中会有说明
本文的代码编辑器为 VS Code ,您可以选择其他现代编辑器/IDE以体验

为什么需要 Type Hint

简而言之,按我的理解,type hint的目的是写给“别人”看。这个“别人”,就包括代码编辑器其他阅读代码的人几天后的你自己
废话不多说,Show You My Code!

开始写代码

现在我们假设,你想写一个函数,用处是统计给定字符串中某个字符出现的次数,于是你大手一挥,写下了这样的代码:

def count_char(text, char):
    return text.啥来着???
复制代码

尴尬的是,你记得str类有这个方法,但却忘记了这个方法叫啥了,看看编辑器的自动提示?

遗憾的是,编辑器不知道你的text是啥类型的,自然没法帮你补全。那我们能不能告诉它:这是个str呢?可以,给参数名后面加个: str就好了 (这个空格不是必须的,只是为了好看)

这就是Type Hint的作用,通过显示指明类型告诉调用者和编辑器:我需要什么类型。这能帮助你充分利用现代编辑器的自动提示功能,并让你写出的代码更加易于阅读和维护。

一个注意点

在继续下面内容之前,我们得明确一件事:Type Hint只是手动指明我们需要的类型,但它不是强制的。举个栗子,对于这个函数,正确的使用如下:

def count_char(text: str, char):
    return text.count(char)

# text 参数为 str
print(count_char("Hello World", "e"))
复制代码

但如果我们给text传了个别的类型,比如int,会发生什么?答案是仍然能编译通过,只是执行时报错而已。

这就是为什么它叫Type Hint:只是提示,并非强制。
当然,我们也可以借助其他手段来实现强制的类型限定,比如借助 mypy

mypy

安装mypy很容易,只需要pip install mypy即可。之后就可以用mypy filename.py检测此类错误

当然,能在vscode中直接用更好。我们可以按ctrl+shift+p打开设置(工作区or全局,看你想法)

配置如下:

{
    "python.linting.mypyEnabled": true
}
复制代码

即可在vscode中实时用mypy检查

基本使用

对于普通类型,用法就像刚刚说的,在名字后面加个: 类型即可

# 变量
num: int = 10
f: float = 0.7

# 自定义的类也差不多
class Node:
    def __init__(self):
        pass
        
# 函数参数
def visit_node(n: Node):
    pass
复制代码

对于集合类型,我们可以使用[]指定里面元素的类型

# 列表,每个元素应该为`str`
def join_to_str(arr: list[str]):
    return "".join(arr)

join_to_str(["Funny", "SaltyFish"]) # 正确的
join_to_str([1, 2, 3]) # 错误 List item 2 has incompatible type "int"; expected "str"
复制代码

Tuple用法也类似

# 值为 int,str的二元组
two_tuple: tuple[int, str] = 200, "哈哈哈"
# 值为 int 的不定长元组
t: tuple[int, ...] = (1, 2, 3, 4)
复制代码

字典可以分别指定keyvalue的类型

# 指定键为`str`,值为`int`
def dict_example(d: dict[str, int]):
    pass

dict_example({"github": 1, "juejin": 2}) # 合法的
# Dict entry 0 has incompatible type "str": "str"; expected "str": "int"
dict_example({"github": "github.com/FunnySaltyFish", "juejin": 2}) 
复制代码

注意的是,上面两个listdict的例子在较早期的Python版本是会报错的,需要先from typing import List,再用List[str]标注类型。不过,如果你的程序希望支持到较多的Python版本,那么用List或许是更好的选择;反之就用list吧。

如果你希望此参数既可以传List,又可以传Tuple,或者传生成器,那么可以写成Iterable,它表示可迭代的

from typing import Iterable
def iter_print(arr: Iterable):
    for each in arr:
        print(each)

iter_print(["你好","我好","她也好"])    # 列表
iter_print(("Funny", "Salty", "Fish")) # 元组
iter_print((i*2 for i in range(10)))   # 生成器
复制代码

类似的更广泛类型还有SequenceMapping,可以自行了解

标注函数

标注函数包括几个方面:标注函数的返回类型,和标注函数类型的参数

标注函数的返回值类型

实际上,如果你把鼠标放到刚刚我们写的count_char函数上,你能看到这样的提示

智能的编辑器通过分析函数内容已经推断出了函数会返回一个int,标了个-> int,而这就是函数的返回值标注方式

# 返回值int
def count_char(text: str, char: str) -> int:
    return text.count(char)
复制代码

如果函数没有return语句,则返回值为None

# 没有返回值,默认为None
def print_n() -> None:
    print("n")
复制代码

如果函数是拿来抛异常,或者更极端,运行完之后程序直接退了,那可以标NoReturn

from typing import NoReturn
def exit_app() -> NoReturn:
    exit(0)
    
def exception_func() -> NoReturn:
    raise Exception("异常退出")
复制代码

这样做的好处之一是,编辑器可以识别到函数调用处后面的代码不会被执行,标灰并给出Unreachable的提示

标注函数类型

如果一个参数,它要求传入的为一个函数,则可以使用Callable描述。比如下面的函数,可以对另一个函数计时

import time
from typing import Callable
def calc_time(func: Callable):
    start = time.time()
    func()
    end = time.time()
    print(f"此函数花费了{end-start}s")

calc_time(lambda : sum(range(10000000)))
复制代码

Callable也可以指定更具体的类型,Callable[[int, int], str] 就表示参数为两个int,返回值为str的函数

更复杂的类型

接下来我们看一些更复杂的类型。
问题一: 如果一个参数可以接受所有类型,可以标成啥?
你可以不标,或者标成typing下的Any,它俩是一样的,标了类似于没标。

问题二: 如果一个参数可以接受几种不同的类型,怎么标?
这个问题在Python 3.10前有些麻烦,你需要引入Union。比如,假设这个参数可以接受intfloat

from typing import Union
def f2(a: Union[int, float]):
    pass

f2(1)   # ok
f2(3.4) # ok
f2("s") # 错误
复制代码

不过,3.10简化了这个操作。现在可以用|并列多个类型

def f2(a: int|float):
    pass
复制代码

二者是等价的

print(Union[int, float] == int|float) # True
复制代码

如果需要了解Py3.10的新特性,可以参考我的这篇文章 Python3.10正式版发布!新特性速览 - 掘金 (juejin.cn)

问题三: 如果参数可以为None呢? 使用|,比如int|None,或者使用Optional

from typing import Optional
a: Optional[int]
a = 1
a = None
复制代码

问题四: 如果这个类型还没被定义,咋整?
这个情景来自类的构造函数,如果需要用到它自己,就会碰到这个问题(或者某些方法需要返回自己)。比如说定义链表的节点:

这时候可以""包围,避免循环引用

或者可以用TypeVar

TListNode  = TypeVar("TListNode", bound="ListNode")

class ListNode:
    def __init__(self, prev_node: TListNode|None) -> None:
        pass

node = ListNode(ListNode(None))
复制代码

不过,目前仍在预览版(估计不久后就有正式版了)的Python 3.11.0加入了typing.Self,用来指自己。到时候可以这样写

from typing import Self
class ListNode:
    def __init__(self, prev_node: Self|None) -> None:
        pass
复制代码

问题五: 有些类型名太长了,我打多了心累,咋办?
比如说,写http接口的响应函数,然后都处理成(状态码, 数据字典)的类型,就会是这样

def http_response():
    return 200, {"code": "0", "data": "https://juejin.cn/user/2673613109214333", "message": "https://github.com/FunnySaltyFish"}
复制代码

那它的类型就得写成:

tuple[int, dict[str, Any]]
复制代码

写多了确实麻烦。所以我们可以给这个类型起个别名:

from typing import TypeAlias, Any
ResponseType: TypeAlias = tuple[int, dict[str, Any]]

def http_response() -> ResponseType:
    pass
复制代码

这样就统一、简洁多了
注意的是,TypeAliasPython3.10强制的,此前的版本可以去掉。但我觉得有TypeAlias的版本比较清晰,能指明这是个类型别名

问题六: 如果某个变量只能取特定值,怎么写? 比如sex,你想让对方只传入其他三种字符串,可以这么写

sex: Literal["男", "女", "其他"]
sex = "未知" # 错误
复制代码

当然这需要importLiteral就是字面量的意思。

除上面写到的,可以用typing.Final创建一个“常量”,告知此值不可被更改

typing包下还有其他一些东东,此处就不赘述了。感兴趣的同学可以翻阅 官方文档 以了解更多。

最后

现在,你已经掌握了Type Hint的基本用法,或许可以开始使用它了。即使不是全部,但从一部分开始也是个不错的选择。毕竟代码最终是给人看的,你也不希望几个月后看着自己曾经写过的代码默默骂一句: “chao,这里应该传个啥类型来着?”

参考

  • 官方文档
  • www.bilibili.com/video/BV16Y…
  • python/mypy: Optional static typing for Python (github.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/110362.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kali系统渗透window实现屏幕监控

1安装 VMware15.5,kali系统,桥接模式 链接:https://pan.baidu.com/s/1Y3ftPnzCj0NaMQNDAhIUjw 提取码:3k2w 2修改sshd_config vim /etc/ssh/sshd_config 去掉这两行注释 完成后保存 esc : wq! 重启SSH服务 /etc/init.d/ssh restart Fina…

一文看懂Linux内核页缓存(Page Cache)

我们知道文件一般存放在硬盘(机械硬盘或固态硬盘)中,CPU 并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被 CPU 访问。 由于读写硬盘的速度比读写内存要慢很多(DDR4 内存读写…

C语言期末集训3(大一,超基础,小猫猫大课堂配套练习)——循环结构

更新不易,麻烦多多点赞,欢迎你的提问,感谢你的转发, 最后的最后,关注我,关注我,关注我,你会看到更多有趣的博客哦!!! 喵喵喵,你对我…

[第十二届蓝桥杯/java/算法]F——时间显示

🧑‍🎓个人介绍:大二软件生,现学JAVA、Linux、MySQL、算法 💻博客主页:渡过晚枫渡过晚枫 👓系列专栏:[编程神域 C语言],[java/初学者],[蓝桥杯] &#x1f4d…

五步法搞定BI业务需求梳理

五步法搞定BI业务需求梳理。高手就是把复杂的事情简单化,简单的东西重复做、认真做。 五步法是哪五步 第一, 明确用户。商业智能BI项目的规划一切以用户需求为导向,首先需要明确各层次的需求用户。用户都不能明确,调研的入口就没…

前端工程化与 webpack:webpack 的基本使用

1. 什么是 webpack 概念:webpack 是前端项目工程化的具体解决方案。 主要功能:它提供了友好的前端模块化开发支持,以及代码压缩混淆、处理浏览器端 JavaScript 的兼容性、性 能优化等强大的功能。 好处:让程序员把工作的重心放…

自动控制原理笔记-信号流图-Mason公式-控制系统的传递函数

目录 信号流图与结构图的比较: 掌握结构图与信号流图的转换: Mason增益公式: 式子详解: 使用Mason增益公式步骤: 使用Mason增益公式的例题: ​编辑 控制系统的传递函数 : 开环传递函数…

当红齐天再捧“绽放杯”金奖:全流程算力网络夯实元宇宙“底座”

近日,由工信部主办的第五届“绽放杯”5G应用征集大赛在深圳落幕。本届大赛以“5G赋能数字化,扬帆助力新征程 ”为主题,超7000家单位的2.8万个项目参赛,共享5G赋能实体经济的新技术、新成果。英特尔联合行业合作伙伴再获佳绩。 其…

java ssm热带水果网上商城系统--

目录 第一章 绪论 5 1.1 研究背景 5 1.2系统研究现状 5 1.3 系统实现的功能 6 1.4系统实现的特点 6 1.5 本文的组织结构 6 第二章开发技术与环境配置 7 2.1 Java语言简介 7 2.2JSP技术 8 2.3 MySQL环境配置 8 2.4 MyEclipse环境配置 9 2.5 mysql数据库介绍 9 2.6 B/S架构 9 第三…

Matter理论介绍-通用-1-06:桥接设备-其他功能

【源码、文档、软件、硬件、技术交流、技术支持,入口见文末】 【所有相关IDE、SDK和例程源码均可从群文件免费获取,免安装,解压即用】 持续更新中,欢迎关注! 一、桥接设备的功能更新 桥接设备能够独立与桥接器进行软…

一文带你快速搭建框架(最全MyBatis笔记修改篇)

前言:最近收到小伙伴们的私信说这一篇有点问题,因为我是用Typora搬运笔记没考虑到这个问题,感谢这个小伙伴反映的问题~ 目录 一.概述 1.简介 2.maven构建 二.相关概念 1.Mapper接口 2.ORM思想 三.映射配置文件 1.文件结构 2.映射配…

31. 填充和步幅的代码实现

1. 填充 我们创建一个高度和宽度为3的二维卷积层,并在所有侧边填充1个像素。给定高度和宽度为8的输入,则输出的高度和宽度也是8。 import torch from torch import nn# 为了方便起见,我们定义了一个计算卷积层的函数。 # 此函数初始化卷积层…

互异数

这道题是实验舱举办的"编程一小时"千人马拉松竞赛的第三题! 目录 #C、互异数 题目描述 输入格式 输出格式 输入样例1 输出样例1 输入样例2 输出样例2 数据规模 思路: 1.最大互质数 2.互质数的数量 3.贪心策略 总代码: 总结: 题目链接: #C、互异数 题目…

Typora使用之在腾讯云建立远程图床【多图】

1 安装PicGo PicGo是一款功能非常强大的图床的工具,支持SM.MS、腾讯COS、GitHub图床、七牛云图床、Imgur图床、阿里云OSS等多种图床平台。 下载地址:https://github.com/Molunerfinn/PicGo/releases 一般安装PicGo-Setup-2.3.0-beta.7-ia32.exe。 可以选…

C++模板特化

前言 模板特化对函数和函数都可以使用。它的作用是以某一模板函数或某个模板类为例,大部分情况下需要写的函数或内容是一致的,但是有些特别情况,所以我们需要单独拎出来。 模板参数 模板参数可分为类型形参和非类型形参。 类型形参&#x…

消息中间件(消息队列)

简介 MQ(message queue)消息队列,也叫消息中间件。消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。它是类似于数据库一样需要…

残差网络~

搬来这个 给自己学学啊,残差网络解决了什么,为什么有效 从深度神经网络的两大难题入手,说说残差网络的形式化定义与实现,并深入探讨其作用的机制,并结合文献对残差网络有效性进行了一些可能的解释。 残差网络是深度学习中的一个…

【论文阅读】(2020)Knapsack polytopes: a survey(下)

文章目录六、Valid inequalities, separation and computations 有效的不等式,分离和计算七、Complete linear descriptions of particular knapsack polytopes 特定背包多形体的完整线性描述7.1 Extended formulations7.2 Complete linear descriptions 完整的线性…

JavaFx TreeView TreeItem 设置额外属性

在使用JavaFx 编写GUI程序时,不可避免的需要创建一个树组件,下面是一个简单的树组件的代码。 import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.s…

clickhouse笔记05--快速部署3节点集群

clickhouse笔记05--快速部署3节点集群1 介绍2 方法步骤2.1 部署 zookeeper 集群2.2 拉起 clickhouse 集群2.3 测试集群3 注意事项4 说明1 介绍 clickhouse笔记01–快速部署clickhouse 介绍了如何快速部署单节点clickhouse服务,本文基于该博文继续介绍如何快速部署3…