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_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
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
|