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 类的类型标注有哪些最佳实践方案?
对于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. 泛型类
使用TypeVar和Generic定义泛型类:
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处理循环引用 - 泛型类用
TypeVar和Generic - 特殊方法如
@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 )。
换语言
升级到最新的 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 靠谱。推荐了解下。


