Skip to main content

A record type for Python

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

record_type-2023.1.post1.tar.gz (6.7 kB view details)

Uploaded Source

Built Distribution

record_type-2023.1.post1-py3-none-any.whl (6.5 kB view details)

Uploaded Python 3

File details

Details for the file record_type-2023.1.post1.tar.gz.

File metadata

  • Download URL: record_type-2023.1.post1.tar.gz
  • Upload date:
  • Size: 6.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/4.0.2 CPython/3.11.7

File hashes

Hashes for record_type-2023.1.post1.tar.gz
Algorithm Hash digest
SHA256 aa80ab1fe772b7ca14e3b91852fd6f243b4106b62c84c09534d79d02488cddae
MD5 9979e4108c94b869f7fffc29543f4310
BLAKE2b-256 f4dcd0eb2136b8172f282b0e63e9797e38321f07864bcc1decd6bdc3c24e9ab3

See more details on using hashes here.

File details

Details for the file record_type-2023.1.post1-py3-none-any.whl.

File metadata

File hashes

Hashes for record_type-2023.1.post1-py3-none-any.whl
Algorithm Hash digest
SHA256 96747aa77a2417e9e16084255f168998ad3ca397ea41af3aa90ee617ec49887e
MD5 93a13df9c43e84f0cc9123c200ff5f42
BLAKE2b-256 3cc400760645165b996356b2963110359afbdad828e004da66ca82c0a837530c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page