Skip to main content

record-type

A proof-of-concept record type for Python.

Goals

  • Create a simple data type that's easy to explain to beginners
  • Creating the data type itself should be fast
  • Type annotations are supported, but not required
  • Instances are immutable to make them (potentially) hashable
  • Support Python's entire parameter definition syntax for instance instantiation, and do so idiomatically
  • Support structural typing as much as possible (e.g., equality based on object "shape" instead of inheritance)

Example

Let's say you're tracking items in your store. You may want to know an item's name, price, and quantity on hand (this is an example from the dataclasses documentation). That can be represented as a simple data class to store all that information together.

The record type is meant to help facilitate creating such simple data classes:

from records import record

@record
def InventoryItem(name: str, price: float, *, quantity: int = 0):
    """Class for keeping track of an item in inventory."""

This creates an InventoryItem class whose call signature for intantiation matches that of the function. Every parameter becomes the corresponding name of an attribute that the argument gets assigned to. It also has:

  • __slots__ for performance
  • __match_args__ for pattern matching
  • __annotations__ for runtime type annotations
  • __eq__() for equality
  • __hash__() for hashing
  • __repr__() which is suitable for eval()
  • Immutability

Compared to other approaches

dataclasses.dataclass

You can create a dataclass for this without much issue:

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

The drawbacks compared to record are:

  • The use of KW_ONLY is awkward
  • It requires using type annotations
  • To make it immutable -- which implies being hashable -- and use __slots__ requires remembering to opting in with the appropriate parameters
  • No support for *args or **kwargs

Named tuples

collections.namedtuple

Using namedtuple allows for a quick way to create the class:

from collections import namedtuple

InventoryItem = namedtuple("InventoryItem", ["name", "price", "quantity"])

The drawbacks compared to record are:

  • Unable to support keyword-only, positional-only, *args, and **kwargs parameters
  • No support for type annotations
  • No support for __match_args__
  • Requires supporting both the attribute- and index-based APIs for any code going forward that returns an instance of the class
  • No docstring
typing.NamedTuple

You can use NamedTuple to create a class that supports type annotations for a named tuple:

from typing import NamedTuple

class InventoryItem(NamedTuple):
    """Class for keeping track of an item in inventory."""
    name: str
    price: float
    quantity: int = 0

The drawbacks compared to record are:

  • Unable to support keyword-only, positional-only, *args, and **kwargs parameters
  • Requires type annotations
  • No support for __match_args__
  • Requires supporting both the attribute- and index-based APIs for any code going forward that returns an instance of the class

types.SimpleNamespace

You can create a simple function that wraps SimpleNamespace

from types import SimpleNamespace

def InventoryItem(name: str, price: float, *, quantity: int = 0):
    return SimpleNamespace(name=name, price=price, quantity=quantity)

The drawbacks compared to record are:

  • No support for __slots__
  • No support for __match_args__
  • No docstring
  • No runtime type annotations
  • Mutable (and so no hashing)

Manual

You can implement the equivalent of record manually:

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))

The drawbacks compared to record are:

  • It's much more verbose to implement

Release files for record-type 2023.1.post1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for record-type 2023.1.post1
File Size Uploaded
record_type-2023.1.post1.tar.gz 6.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for record-type 2023.1.post1
File Interpreter ABI Platform
record_type-2023.1.post1-py3-none-any.whl Python 3 none any Details

Total release size: 13.2 kB

Release files / record_type-2023.1.post1.tar.gz

Download URL record_type-2023.1.post1.tar.gz
Size 6.7 kB
Tags Source
SHA-256 checksum
How to use checksums
aa80ab1fe772b7ca14e3b91852fd6f243b4106b62c84c09534d79d02488cddae
BLAKE2b-256 checksum
How to use checksums
f4dcd0eb2136b8172f282b0e63e9797e38321f07864bcc1decd6bdc3c24e9ab3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/4.0.2 CPython/3.11.7

Release files / record_type-2023.1.post1-py3-none-any.whl

Download URL record_type-2023.1.post1-py3-none-any.whl
Size 6.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
96747aa77a2417e9e16084255f168998ad3ca397ea41af3aa90ee617ec49887e
BLAKE2b-256 checksum
How to use checksums
3cc400760645165b996356b2963110359afbdad828e004da66ca82c0a837530c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/4.0.2 CPython/3.11.7

Release history Release notifications | RSS feed

This release

2023.1.post1 This release

2 release files

2023.1

2 release files

2023.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page