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相比的缺点是
- 实现起来更加冗长。