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 foreval()- 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_ONLYis 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
*argsor**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**kwargsparameters - 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**kwargsparameters - 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)
| File | Size | Uploaded | |
|---|---|---|---|
| record_type-2023.1.post1.tar.gz | 6.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|