Python 类的类型标注有哪些最佳实践方案?

比如说:

import numpy as np

class Person: name: str age: int hobbies: np.ndarray

person = Person() person.age = “12” # raise TypeMismatched exception

Github 上面找到几个在 Python3 测试一下然而并不能用。现在开发 debug 中遇到的 10 个 需要呕心沥血 debug 的中有 9 个都是因为类型问题,镶嵌类型尤其难搞。def 虽然有 type hints,但是函数过多时会封装成这样:

class Params:
    param1 = 'hello'
    param2 = '...'
    ...
    param7 = '...'

所以为了保持代码干净,最好还是在 class 里加入类型的检查。大家除了 IDE、眼球 parse 和记忆力还有什么好办法?


Python 类的类型标注有哪些最佳实践方案?

17 回复

类型标注这个只有在 3.6 及以上的版本才支持的. 解决办法就是升级你的 python 到 3.6


对于Python类的类型标注,我通常遵循这几个核心原则:

1. 类属性标注 直接在类体中标注,使用ClassVar区分实例属性和类属性:

from typing import ClassVar, Optional

class User:
    # 类属性
    default_role: ClassVar[str] = "guest"
    user_count: ClassVar[int] = 0
    
    # 实例属性
    def __init__(self, name: str, age: int):
        self.name: str = name
        self.age: int = age
        self.email: Optional[str] = None

2. 方法返回类型 特别是__init__返回None,其他方法明确标注返回类型:

class Calculator:
    def __init__(self) -> None:
        self.value: float = 0.0
    
    def add(self, x: float) -> "Calculator":
        self.value += x
        return self
    
    @classmethod
    def create(cls) -> "Calculator":
        return cls()

3. 处理前向引用 类内部相互引用时用字符串,或者from __future__ import annotations

from __future__ import annotations

class Node:
    def __init__(self, value: int):
        self.value = value
        self.next: Node | None = None  # 直接使用Node,不需要字符串

4. 泛型类 使用TypeVarGeneric定义泛型类:

from typing import TypeVar, Generic

T = TypeVar('T')

class Container(Generic[T]):
    def __init__(self, item: T):
        self.item: T = item
    
    def get(self) -> T:
        return self.item

5. 特殊方法标注 @property@staticmethod也需要标注:

class Rectangle:
    def __init__(self, width: float, height: float):
        self._width = width
        self._height = height
    
    @property
    def area(self) -> float:
        return self._width * self._height
    
    @staticmethod
    def is_square(width: float, height: float) -> bool:
        return width == height

6. 使用@dataclass简化 Python 3.7+推荐用@dataclass自动生成__init__

from dataclasses import dataclass
from typing import Optional

@dataclass
class Product:
    id: int
    name: str
    price: float
    description: Optional[str] = None

关键点总结:

  • 类属性用ClassVar,实例属性在__init__中标注
  • 所有方法都标注返回类型,__init__返回None
  • 用字符串或from __future__ import annotations处理循环引用
  • 泛型类用TypeVarGeneric
  • 特殊方法如@property也要标注
  • 考虑用@dataclass减少样板代码

一句话建议: 保持类型标注一致且完整,优先用@dataclass简化代码。

有个叫 attrs 的库

Data Classes

换有类型的语言

可选类型的语言是多么明智。。

class People:
def _init(self, name: str=None, age: int=0) ->None:


def find_all_people() ->[People]:


这样实现类型注释,方便 ide 预检查错误,参考
pep 484 type hint
https://www.python.org/dev/peps/pep-0484/
Python 3.5 就提供这种语法支持了。而当前主流 Linux 发行版预置的 Python3 都是 3.5+以上版本了,所以真的可以全面转到 py3 了。到 V3.6 还提供 f-string 语句支持( pep 498 )。

换语言

有个比较 lowbility 但是有效的办法就是,在函数或类最开始
就把要用到所有变量名加上类型前缀或后缀并赋初始值,比如下面这样:
def foo():
int_a = 0,
a_int=0 ,
str_b=’’,
b_str=’'

过程中严禁同一变量名改换类型使用,这样 IDE 就可以比较好的类型推断了。

不过这样写 python 的话还不如直接用够浪( golang )

升级到最新的 Python

或者不用在意类型,用的时候强制转换就行了…比如这个 age,你先 age = int(p.age)不就完了

当把 object 作为参数时,python3.6 是不能检查 object 的 attr 的类型的。变量名包括类型已经有了,然而变量多层传递后也不会有大的作用。因此:

<br>class Person:<br> name: str<br> age: int<br><br>
以上这种语法是无效的,Python3.6 也根本不支持这种语法,我不知道大家回答这个是因为误解了还是不知道这一点。
但下面这一个可以。

<br>class TypeMismatched(Exception):<br> pass<br><br>class Person:<br><br> def __init__(self, name: str, age: int):<br> <a target="_blank" href="http://self.name" rel="nofollow noopener">self.name</a> = name <br> self.age = age <br><br> def __setattr__(self, name, value):<br> # Invalid for class comparsion<br> if hasattr(self, name) and type(self.__dict__[name]) != type(value):<br> raise TypeMismatched<br> self.__dict__[name] = value<br><br>person = Person(name='Jane', age=12)<br>print(<a target="_blank" href="http://person.name" rel="nofollow noopener">person.name</a>)<br>print(person.age)<br>person.age = '14'<br>print(person.age) # raise TypeMismatched Exception<br><br>
因此想了想,用 type hints 作为 class 属性的首次检查,重载__setattr__,加入检查方法,作为以后 mutable 值的检查。上面的 type(variable_a) == type(variavble_b)有缺陷,需要再改进一下,但基本思路就这样了。

为什么 markdown 不能用……

可以在 class 里面加入一个参数, 虽然有点不雅观,mutable -> bool 来决定是否 mutable。一样地,在__setattr__里面做检查。

换语言不是说换就换了,写 numpy 的话 golang 怎么写…… postfix 加类型的做法已经实行很久了,但效果有限,尤其对镶嵌类型的 list 等,numpy 有太多自定义类型,然而 python 本身却不可标注,再加上镶嵌,灾难就发生了

pydantic 了解一下。还有校验功能。结合 mypy 使用。

谢谢,这个库有些有用的工具可以用来自己封装。因为测试后发现,初始化为 str 后赋予 int 还是不会抛 error。当传入错误的类型,希望程序能够崩溃。

不编译检查了又能怎么样呢?
还是 Cython 靠谱。推荐了解下。

回到顶部