Python的记录类型
项目描述
record-type
Python的一个record
类型的概念证明。
目标
- 创建一个简单易解释的数据类型,便于初学者理解
- 创建数据类型本身应该快速
- 支持类型注解,但不是必需的
- 实例是不可变的,使其(可能)可哈希
- 支持实例实例化时Python的整个参数定义语法,并以习惯用法实现
- 尽可能支持结构化类型(例如,基于对象的“形状”而不是继承的等价性)
示例
假设您正在跟踪商店中的商品。您可能想知道商品的名称、价格和库存数量(这是一个来自dataclasses
文档的示例)。这可以用一个简单的数据类来表示,以存储所有这些信息。
record
类型旨在帮助创建这样的简单数据类
from records import record
@record
def InventoryItem(name: str, price: float, *, quantity: int = 0):
"""Class for keeping track of an item in inventory."""
这创建了一个与函数实例化签名匹配的InventoryItem
类。每个参数都成为被分配给参数的属性的对应名称。它还具有
__slots__
以提高性能__match_args__
用于模式匹配__annotations__
用于运行时类型注解__eq__()
用于等价性__hash__()
用于哈希__repr__()
适合用于eval()
- 不可变性
与其他方法相比
dataclasses.dataclass
您可以使用dataclass
轻松创建此数据类,而不会遇到太多问题
from dataclasses import dataclass, KW_ONLY
@dataclass(frozen=True, slots=True)
class InventoryItem:
"""Class for keeping track of an item in inventory."""
name: str
price: float
_: KW_ONLY
quantity: int = 0
与record
相比的缺点是
KW_ONLY
的使用很尴尬- 它需要使用类型注解
- 要使其不可变(这意味着可哈希)并使用
__slots__
,需要记住使用适当的参数进行选择。 - 不支持
*args
或**kwargs
。
命名元组
collections.namedtuple
使用 namedtuple
可以快速创建类
from collections import namedtuple
InventoryItem = namedtuple("InventoryItem", ["name", "price", "quantity"])
与record
相比的缺点是
- 不支持关键字唯一、位置唯一、
*args
和**kwargs
参数。 - 不支持类型注解。
- 不支持
__match_args__
。 - 需要支持任何返回该类实例的代码的属性和基于索引的 API。
- 没有文档字符串。
typing.NamedTuple
可以使用 NamedTuple
创建支持命名元组的类型注解的类
from typing import NamedTuple
class InventoryItem(NamedTuple):
"""Class for keeping track of an item in inventory."""
name: str
price: float
quantity: int = 0
与record
相比的缺点是
- 不支持关键字唯一、位置唯一、
*args
和**kwargs
参数。 - 需要类型注解。
- 不支持
__match_args__
。 - 需要支持任何返回该类实例的代码的属性和基于索引的 API。
types.SimpleNamespace
可以创建一个简单的函数,该函数包装 SimpleNamespace
from types import SimpleNamespace
def InventoryItem(name: str, price: float, *, quantity: int = 0):
return SimpleNamespace(name=name, price=price, quantity=quantity)
与record
相比的缺点是
- 不支持
__slots__
。 - 不支持
__match_args__
。 - 没有文档字符串。
- 没有运行时类型注解。
- 可变(因此不可哈希)。
手动
可以手动实现等价的 record
。
from typing import Any, NoReturn
class InventoryItem:
"""Class for keeping track of an item in inventory."""
__slots__ = ("name", "price", "quantity")
__match_args__ = ("name", "price")
name: str
price: float
quantity: int
def __init__(self, name: str, price: float, *, quantity: int = 0) -> None:
object.__setattr__(self, "name", name)
object.__setattr__(self, "price", price)
object.__setattr__(self, "quantity", quantity)
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.name!r}, {self.price!r}, quantity={self.quantity!r})"
def __setattr__(self, _attr: Any, _val: Any) -> NoReturn:
raise TypeError(f"{self.__class__.__name__} is immutable")
def __eq__(self, other: Any) -> bool:
if self.__slots__ != getattr(other, "__slots__", None):
return NotImplemented
return all(
getattr(self, attr) == getattr(other, attr)
for attr in self.__slots__
)
def __hash__(self) -> int:
return hash(tuple(self.name, self.price, self.quantity))
与record
相比的缺点是
- 实现起来更加冗长。