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
gosky
V2EX  ›  Python

Python 中 dataclass 风格定义异常类,正确吗?

  •  
  •   gosky · 153 天前 · 1428 次点击
    这是一个创建于 153 天前的主题,其中的信息可能已经有所发展或是发生改变。

    python 源码中有下面这样的异常定义:

    class OSError(Exception):
        errno: int
        strerror: str
        # filename, filename2 are actually str | bytes | None
        filename: Any
        filename2: Any
        if sys.platform == "win32":
            winerror: int
    

    因此我经常按上面的风格定义异常类,比如这次的:

    class Error(Exception):
        code: str
        message: str
    

    这样子写,一直工作良好。也许是因为我之前从没访问里面的属性? 这次,我写了下面的代码:

    assert e.code in ("unauthorized", "permission")
    

    收到很温馨的提示:

        assert e.code in ("unauthorized", "permission")
               ^^^^^^
    AttributeError: 'Error' object has no attribute 'code'
    

    问了 Claude ,回答为:

    你提到的这种异常定义方法确实在 Python 源码中存在,特别是在类型提示和静态类型检查方面。这种定义方式主要用于类型注解和静态类型检查工具(如 mypy )。让我解释一下这种定义的目的和工作原理:
    
    1. 类型注解的目的:
       这种定义主要是为了提供类型信息,而不是实际定义属性。它告诉类型检查器这个类预期会有哪些属性,以及这些属性的预期类型。
    
    2. 运行时行为:
       这些注解在运行时不会创建实际的属性。实际的属性创建通常在类的 `__init__` 方法或其他方法中完成。
    
    3. 兼容性和灵活性:
       这种方式允许在不改变运行时行为的情况下添加类型信息,这对于大型的、需要保持向后兼容性的项目(如 Python 标准库)非常有用。
    
    4. 静态类型检查:
       静态类型检查工具可以使用这些注解来检查代码,即使这些属性可能在运行时动态创建。
    
    5. 文档作用:
       这些注解也起到了文档的作用,告诉开发者这个类预期会有哪些属性。
    
    在 `OSError` 的例子中,这些属性可能在异常被实际创建时(可能是在 C 代码中)被设置,而不是在 Python 的 `__init__` 方法中。
    
    需要注意的是,这种定义方式主要用于 Python 的内置类型和标准库,在普通的 Python 代码中,我们通常会使用 `__init__` 方法来初始化属性,就像我在前面的回答中建议的那样。
    
    对于你自己的代码,除非你有特殊需求(比如为了兼容性或与特定的类型检查工具配合),否则最好还是使用常规的方式定义和初始化属性。
    
    你对这种定义方式还有其他疑问吗?或者你想了解更多关于 Python 类型注解的信息?
    
    6 条回复    2024-07-27 22:26:26 +08:00
    retanoj
        1
    retanoj  
       153 天前
    你的 e.code 中的 e 是怎么初始化和赋值的呢?
    huangyezhufeng
        2
    huangyezhufeng  
       153 天前
    第一句话就没理解,Python 源码也没找到你说的这个定义...
    https://github.com/search?q=repo%3Apython%2Fcpython+class+OSError%28Exception%29&type=code
    Vegetable
        3
    Vegetable  
       153 天前   ❤️ 1
    你发的 OSError 是 buitilins.pyi 文件中的类型定义,根本不是 py 代码。

    class 内部的`field: str`形式只是一种类型标注,dataclass 和 pydantic 之类的库支持这种形式的写法,但是 Exception 不支持,你需要自己在 init 方法中处理好。
    Vegetable
        4
    Vegetable  
       153 天前   ❤️ 1
    @huangyezhufeng https://github.com/python/typeshed/blob/9eae1ae1a5b47745c7d289bb2092f2b4340e7622/stdlib/builtins.pyi#L1890

    他把编辑器(估计是 vscode )内置的 pyi 当作是 python 源码了
    ruanimal
        5
    ruanimal  
       152 天前
    因为你 except 的类型不对吧
    lulaolu
        6
    lulaolu  
       152 天前   ❤️ 1
    Claude 说得挺清楚:这种写法只是注解,并不真的生效。

    你既然提到了 dataclass ,那可以用这个库优化下代码:

    ```python
    >>> from dataclasses import dataclass
    >>>
    >>> @dataclass
    ... class Error(Exception):
    ... code: str
    ... message: str
    ...
    >>>
    >>> err = Error(code=1, message="2")
    >>> err.code
    1
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1039 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 20:19 · PVG 04:19 · LAX 12:19 · JFK 15:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.