V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
FlyingBackscratc
V2EX  ›  Python

2024 年了,如何合理地为 Python 代码添加强类型支持?

  •  
  •   FlyingBackscratc · 2024-05-24 04:08:13 +08:00 · 2960 次点击
    这是一个创建于 389 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我知道 Python 本身是强类型的,而且非常强。标题里的强类型是指能够“锁住”动态类型的情况。

    通常我们用 Python 开发工具时都是很享受动态类型的便利性和开发速度的,随着 3.6 开始添加 typehints ,慢慢地,类型系统也在向类 typescript 迁移。我个人是完全不喜欢把 python 写成 java 的写法的,无限的类型约束加上死板的写法,以及强行实现的毫无必要的设计模式,我感觉这是纯脑瘫行为。但是我欣然接受在关键节点、关键组件和关键模块引入严格的类型检查,以我的经验来看可以有效减少开发过程中 bug 产生。

    举例来说就是,python 的灵活性让我们可以向任意变量传入任意类型,如果想传入固定数据结构,可以通过注释约束,但没有解释器保障。往往我们推崇防御性编程,实际参与多人项目提交代码时,如果传入复杂结构就需要进行多重校验,以确保程序执行正常。

    persons = [
    	{
        	"姓名": "张三",
            "年龄": 19
        }
    ]
    

    例如如果想编写一个函数接受上述输入,防御性的写法可能是

    def func(persons: list[...]):
        if not persons:
            return ... # err1
        person_one = persons.pop(0)
        age = person_one.get("年龄")
        if age is None:
            return ... # err2
        # else
        ... # 业务逻辑
    

    加入以上大段代码,可以在发生手抖输入错误,数据清洗不到位,或者程序未知细节处产生未知行为时让函数仍然符合设计工作。缺点可能是行数太多,说实话也完全丧失了灵活性优势。

    我寻思既然已经 2024 年了,typehints 用来描述类型倒是没啥问题。问题是现在社区出没出什么方案,可以在编译器或者运行时阶段检查输入和输出两个节点,把函数掐头去尾一下,我感觉 bug 都会少很多。

    from typing import List, TypedDict
    
    class Person(TypedDict)
        name: str
        age: int
    
    def func(persons: List[Person]):
        age = persons.pop(0).get("age") + 1
        return ...
    

    不知道有没有什么东西能实现上述效果,可以结合 typing 确保输入类型准确,避开检查代码的。目前。目前来说上面这套写法只有解释器检查,在动态的过程中没办法做任何检查,也就是说如果三方库,或者自己写的代码有 bug ,导致了任何的意料外行为时没有办法起到任何的防御性作用,debug 又是地狱了。

    16 条回复    2024-06-07 23:19:49 +08:00
    irainsoft
        1
    irainsoft  
       2024-05-24 04:22:06 +08:00   ❤️ 1
    Pydantic? 印象中如果类型错了是能明确给出原因的
    yanyao233
        2
    yanyao233  
       2024-05-24 06:34:37 +08:00 via Android
    你需要 pydantic
    Evrins
        3
    Evrins  
       2024-05-24 08:31:16 +08:00
    pydantic 非常严格的类型检查
    lisxour
        4
    lisxour  
       2024-05-24 09:45:32 +08:00
    永远不要把多个不同类型的值存入单个变量,即使语言允许你这么干,我 php 、js 、python 都写过,我只能说写时一时爽,维护火葬场,即使是自己的代码,半年后自己都维护不动
    Vcide
        5
    Vcide  
       2024-05-24 10:32:22 +08:00
    Pydantic,支持运行时类型检查.用 Rust 实现,速度也很快
    henix
        6
    henix  
       2024-05-24 10:36:36 +08:00
    这种需求应该属于 data validation 吧,除了 pydantic 还可以看看 json schema
    mark2025
        7
    mark2025  
       2024-05-24 10:46:50 +08:00
    你说的是 静态类型 么
    Vegetable
        8
    Vegetable  
       2024-05-24 10:57:21 +08:00
    你说的似乎是动态类型和静态类型的区别。

    即使是静态语言,也不是动态检查数据的类型,编译期就做了。
    配合 mypy 之类的工具,完全可以将 py 当作静态类型来写,现如今 pydantic 能良好的控制程序的输入数据,typehint+静态检查确保代码本身静态类型,就符合你说的标准了。只是这样我何必还写 python 呢
    freefcw
        9
    freefcw  
       2024-05-24 14:09:25 +08:00
    @Vegetable 最近出了几个项目,确实是深有同感,还不如 springboot 一套的效率高了
    NoOneNoBody
        10
    NoOneNoBody  
       2024-05-24 14:21:14 +08:00
    你可以自己写个装饰器
    需要用到 inspect 模块
    sig = inspect.signature(func)
    parameters = sig.parameters # func 参数的类型
    return_annotation = sig.return_annotation # func 返回的类型
    然后用个字典,根据类型指定转换的函数……
    主要是 python 类型太多,随便一个 class 就能作为类型,例如指定是 list 类型,但传入可能是 tuple, set, string, dict, iteror, generator, map, pandas.series, numpy.ndarray...全部都可以转为 list ,但全部都要兼顾容错么?
    FlyingBackscratc
        12
    FlyingBackscratc  
    OP
       2024-05-25 01:26:00 +08:00
    @lisxour

    @Vegetable 感觉也不是静态类型,就是在核心位置确保类型安全,方便长期维护而已。看了看 pydantic 似乎是没有更好的选择了,我试了试感觉还是有点麻烦,只能勉强这么用了。

    @freefcw 个人感觉没啥关联性,这个贴主要是 py 的低侵入类型安全,springboot 一套复杂度也不在 java 语法笨,优势可能主要是整个一套生态,消息队列、日志、连接池、鉴权、追踪等等,py 要配齐这一套还挺费劲的。你要说写法,我感觉就算全面落实类似 typescript 写法的 py 也不如 java 繁琐,何况社区基本共识是全面迁移 typescript 类型很蠢,完全放弃优势
    freefcw
        13
    freefcw  
       2024-05-26 16:43:00 +08:00
    @FlyingBackscratc

    你说的没错,这个帖子讨论的是 py 的东西,我们目前主要是 pydantic ,用 mypy 做检查, 多不少额外的步骤,但就像 python 开发团队说的,类型系统只是一个辅助,解释器并不会使用,这就只能借助其他手段,也就是你在找的

    不过这么做的目的是什么呢




    再说点题外的话

    `你要说写法,我感觉就算全面落实类似 typescript 写法的 py 也不如 java 繁琐,何况社区基本共识是全面迁移 typescript 类型很蠢,完全放弃优势`

    社区的基本共识没错,但行百里半九十,实际复杂的更多还是业务代码,怎么将这一部分处理好更是问题。像 requests 作者就明确表示 typehints 的使用问题: https://lwn.net/Articles/643399/

    Java 的类型声明反而是语言本身的要求,更自然,清晰,为此不得不将复杂度暴露给使用者。给 requests 库的接口加 typehints 就面临因为接口太易用,导致 typehints 异常复杂很难维护,具体上面 requests 的作者 lukasa 有举例说明,typehints 从刚开始推出到现在也在不断的进化,但做这么多搞这么复杂,又是为了解决什么问题

    回到 python 和 type hints ,为什么 python 要加 type hints

    复杂度和易用性有点像鱼和熊掌不可兼得,通盘考虑下,java + springboot 的生态远比 python 的强多了,我团队的人整了一套尝试组建一套基于 fastapi 的快速框架,但效果实在不那么理想
    jjx
        14
    jjx  
       2024-05-26 17:21:27 +08:00
    还在用 python 2.7
    woodfizky
        15
    woodfizky  
       2024-06-04 15:46:15 +08:00
    Pydantic 很好用!

    可以自定义 validator ,也支持自定义类型,还可以自己封装一些方法。
    比如序列化成 json ,再从 json 反序列化成对象。

    我现在程序逻辑里用的很多,一个是约束自己犯错,另一个写代码写起来也方便。
    FlyingBackscratc
        16
    FlyingBackscratc  
    OP
       2024-06-07 23:19:49 +08:00
    @freefcw 你回这个没啥意义,说了半天还是 springboot 组件全而已,至于为什么组件全,这是历史原因了。php 本世纪初就统治 web 市场了,到现在互联网上超过 50%的系统还是 php 支持的,是因为 php 在语言层面相对其他语言来说有任何优越性吗? python3.6 印象里 15 还是 16 年出的,黄花菜都凉了。同理现在 python 统治深度学习市场,是因为 python 语言能写的东西其他语言写不了吗?你用 fastapi 组建快速框架不理想,说到底是没有开源项目给你薅,或者是你的人员素质良莠不齐,或者是钱没给够,和这个帖子说的语言特性没有任何关系,不知道你在回什么。说到底 java 缺了一等公民函数和全局变量,表达能力始终是依托,不会改变,如果你有兴趣讨论我建议你单独开贴,v 站老哥应该有很多很愿意和你讨论,而不是在这里回。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   945 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 21:11 · PVG 05:11 · LAX 14:11 · JFK 17:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.