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 · 217 天前 · 2579 次点击
    这是一个创建于 217 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我知道 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  
       217 天前   ❤️ 1
    Pydantic? 印象中如果类型错了是能明确给出原因的
    yanyao233
        2
    yanyao233  
       217 天前 via Android
    你需要 pydantic
    Evrins
        3
    Evrins  
       217 天前
    pydantic 非常严格的类型检查
    lisxour
        4
    lisxour  
       217 天前
    永远不要把多个不同类型的值存入单个变量,即使语言允许你这么干,我 php 、js 、python 都写过,我只能说写时一时爽,维护火葬场,即使是自己的代码,半年后自己都维护不动
    Vcide
        5
    Vcide  
       217 天前
    Pydantic,支持运行时类型检查.用 Rust 实现,速度也很快
    henix
        6
    henix  
       217 天前
    这种需求应该属于 data validation 吧,除了 pydantic 还可以看看 json schema
    mark2025
        7
    mark2025  
       217 天前
    你说的是 静态类型 么
    Vegetable
        8
    Vegetable  
       217 天前
    你说的似乎是动态类型和静态类型的区别。

    即使是静态语言,也不是动态检查数据的类型,编译期就做了。
    配合 mypy 之类的工具,完全可以将 py 当作静态类型来写,现如今 pydantic 能良好的控制程序的输入数据,typehint+静态检查确保代码本身静态类型,就符合你说的标准了。只是这样我何必还写 python 呢
    freefcw
        9
    freefcw  
       217 天前
    @Vegetable 最近出了几个项目,确实是深有同感,还不如 springboot 一套的效率高了
    NoOneNoBody
        10
    NoOneNoBody  
       217 天前
    你可以自己写个装饰器
    需要用到 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
       216 天前
    @lisxour

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

    @freefcw 个人感觉没啥关联性,这个贴主要是 py 的低侵入类型安全,springboot 一套复杂度也不在 java 语法笨,优势可能主要是整个一套生态,消息队列、日志、连接池、鉴权、追踪等等,py 要配齐这一套还挺费劲的。你要说写法,我感觉就算全面落实类似 typescript 写法的 py 也不如 java 繁琐,何况社区基本共识是全面迁移 typescript 类型很蠢,完全放弃优势
    freefcw
        13
    freefcw  
       214 天前
    @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  
       214 天前
    还在用 python 2.7
    woodfizky
        15
    woodfizky  
       206 天前
    Pydantic 很好用!

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

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