请问一下Python 可以进行强类型开发吗?

请问一下Python 可以进行强类型开发吗?

1、我们知道python是弱类型语言。
请问是否可以进行类似TypeScript一样进行强类型开发?

2、我的需求仅仅是为了让定义函数/方法时候,知道传入和传出的是什么数据结构。

3、如果可以请问是否推荐Python用于强类型开发呢?

阅读 1.6k
2 个回答

不能强类型

但是可以打上类型标注,叫做 type hint https://docs.python.org/zh-cn/3.13/library/typing.html

这算是 python3.7 开始支持的,现在都 3.13 了,type hint 已经非常的完善了,只要你的 python 版本>=3.8 就能愉快的使用,当然最好可以大于 3.10

然后配合 pydantic 就能完美实现你要的「我的需求仅仅是为了让定义函数/方法时候,知道传入和传出的是什么数据结构」

基础类型,比如 int、str、float、date、datetime、bool 直接用其本身做 type hint 就行

比如下面的 had_oss_file 函数的入参和返回值都加上了基本的类型标注

def had_oss_file(matched_storage_date: date, website: str, key_id: str) -> bool:
    for i in range(35):
        oss_url = f"thumbnail/{matched_storage_date-timedelta(days=i)}/{website}/{key_id[-2:]}/{key_id}.jpg"
        if bucket.object_exists(oss_url):
            return True
    return False

在调用的时候 vscode 就能识别和给出提示

图片.png

如果是复合类型,比如 dict 这种,那就建议引入 pydantic 了

比如下面这样定义一个 ICPInfo 的模型

from pydantic import BaseModel, Field


class ICPInfo(BaseModel):
    organizer_name: str | None = Field(None, description='主办单位名称')
    nature_of_organizer: str | None = Field(None, description='主办单位性质')
    website_record_number: str | None = Field(None, description='网站备案号')
    site_name: str | None = Field(None, description='网站名称')
    home_page_address: str | None = Field(None, description='网站首页地址')
    date_of_approval: str | None = Field(None, description='审核日期')
    whether_to_restrict_access: str | None = Field(None, description='是否限制接入')

然后写对应的函数

def search(url: str) -> list[ICPInfo]:
    result = []

    dom = etree.HTML(html)
    
    icpinfo = ICPInfo()
    record_number = dom.xpath('//a[@id="icp_icp"]/text()')
    icpinfo.website_record_number = record_number[0] if record_number else ''
    nature_of_organizer = dom.xpath('//span[@id="icp_type"]/text()')
    icpinfo.nature_of_organizer = nature_of_organizer[0] if nature_of_organizer else ''
    organizer_name = dom.xpath('//span[@id="icp_company"]/text()')
    icpinfo.organizer_name = organizer_name[0] if organizer_name else ''
    date_of_approval = dom.xpath('//span[@id="icp_passtime"]/text()')
    icpinfo.date_of_approval = date_of_approval[0] if date_of_approval else ''
    website_name = dom.xpath('//div[@id="webpage_title"]/text()')
    icpinfo.site_name = website_name[0] if website_name else ''

    result.append(icpinfo)
    return result

在使用 search 函数时候,vscode 也可以给出类型提示

图片.png

还能在访问具体字段的时候,给出具体的名称和提示

图片.png

pydantic

借助pydantic框架,可以做类型强校验,但是我一般都只是在api接口处用这个做校验,它本身也是rust写的底层校验逻辑,所以性能很不错。
比如:

from pydantic import BaseModel


class Demo(BaseModel):
    field1: str
    field2: int


@app.post("/index")
async def request_data(data: Demo):
    return [data.field1, data.field2]

类似这种的,如果对方传递的不是这个类型的对象或者对象和字段名称都对,但是字段的数据类型不对,那么也会自动报错,告知你哪里有问题,其实这个帮我们省去了参数校验的步骤,也基本上达到了一个参数类型的强校验。但是这个接收到的参数是一个 BaseModel 的子类也就是 Demo 对象,我觉得比python的Dict好用的多,而且提供了 model_dump()model_dump_json() 方法将其转为python的dict对象和json字符串。

Typing Hint

对于相对简单的参数结构,pydantic的使用并不合适,而python自带的 typing 模块去做类型注释就刚好。一方面vscode可以做类型提示,比如在某些地方用到这些函数的时候,会很方便,但是无法在运行态监控函数的传参与你接收的实际参数是否相同,这也是弊端。。。

from typing import List

def demo(a: str, b: List[str]) -> bool:
    if a in b:
        return True
    return False

比如这个例子,你可以很清楚的知道 demo 这个函数需要的两个参数分别是什么数据类型,以及他的预期返回值类型,这对于大型的python项目来说,比起原本没有类型注释的方式方便的不要太多。

但是问题也在于它不支持类型校验,就是比如 b: List[str] ,我可以传递 [1, 2, 3] 给他也行,asada 也可以。

所以为了代码规范,不出现这种定义参数与传递参数不同的数据结构的情况,还需要搭配python的一些工具,比如 mypyPyright 来使用,它可以检测你调用的地方与你定义的地方,参数类型是否相同,并在代码中有所体现。

image.png

如上所示,这虽然还比不上typescript,但是已经相当不错了。

正常情况下,你的函数定义如下所示:
image.png

然后在别的使用它的地方,鼠标悬浮该函数,vscode中截图如下:
image.png

结论

至于 typingpydantic,可以酌情混合使用,这里只提供一个参考。
不过我个人使用下来的经验是:

  • pydantic适合复杂的数据结构,且你明确知道想要什么字段的情况,且不太需要频繁与python的dict进行转换,毕竟是有一定开销的,如果不太注重的情况下,可以忽略该建议~
  • typing hint适用所有的场景,虽然不是强制runtime报错,但是开发友好,非常推荐

    • TypedDict 可以一定程度上当作Pydantic使用,如果不需要强制校验参数的前提下,仅仅有个代码提示

最后希望对有需要的朋友有帮助。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏