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 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_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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
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
Algorithm | Hash digest | |
---|---|---|
SHA256 | aa80ab1fe772b7ca14e3b91852fd6f243b4106b62c84c09534d79d02488cddae |
|
MD5 | 9979e4108c94b869f7fffc29543f4310 |
|
BLAKE2b-256 | f4dcd0eb2136b8172f282b0e63e9797e38321f07864bcc1decd6bdc3c24e9ab3 |
File details
Details for the file record_type-2023.1.post1-py3-none-any.whl
.
File metadata
- Download URL: record_type-2023.1.post1-py3-none-any.whl
- Upload date:
- Size: 6.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/4.0.2 CPython/3.11.7
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 96747aa77a2417e9e16084255f168998ad3ca397ea41af3aa90ee617ec49887e |
|
MD5 | 93a13df9c43e84f0cc9123c200ff5f42 |
|
BLAKE2b-256 | 3cc400760645165b996356b2963110359afbdad828e004da66ca82c0a837530c |