dataclass tools, extended by multiple dispatch
Project description
dataclassish
Tools from dataclasses
, extended to all of Python
Python's dataclasses
provides tools for working with
objects, but only compatible @dataclass
objects. 😢
This repository is a
superset of those tools and extends them to work on ANY Python object you want!
🎉
You can easily register in object-specific methods and use a unified
interface for object manipulation. 🕶️
For example,
from dataclassish import replace # New object, replacing select fields
d1 = {"a": 1, "b": 2.0, "c": "3"}
d2 = replace(d1, c=3 + 0j)
print(d2)
# {'a': 1, 'b': 2.0, 'c': (3+0j)}
Installation
pip install dataclassish
Documentation
WIP. But if you've worked with a
dataclass
then you
basically already know everything you need to know.
Quick example
In this Example we'll show how dataclassish
works exactly the same as
dataclasses
when working with a @dataclass
object.
from dataclassish import replace
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p = Point(1.0, 2.0)
print(p)
# Point(x=1.0, y=2.0)
p2 = replace(p, x=3.0)
print(p2)
# Point(x=3.0, y=2.0)
Now we'll work with a dict
object. Note that you cannot use tools
from dataclasses
with dict
objects.
from dataclassish import replace
p = {"x": 1, "y": 2.0}
print(p)
# {'x': 1, 'y': 2.0}
p2 = replace(p, x=3.0)
print(p2)
# {'x': 3.0, 'y': 2.0}
# If we try to `replace` a value that isn't in the dict, we'll get an error
try:
replace(p, z=None)
except ValueError as e:
print(e)
# invalid keys {'z'}.
Registering in a custom type is very easy! Let's make a custom object and define
how replace
will operate on it.
from typing import Any
from plum import dispatch
class MyClass:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def __repr__(self) -> str:
return f"MyClass(a={self.a},b={self.b},c={self.c})"
@dispatch
def replace(obj: MyClass, **changes: Any) -> MyClass:
current_args = {k: getattr(obj, k) for k in "abc"}
updated_args = current_args | changes
return MyClass(**updated_args)
obj = MyClass(1, 2, 3)
print(obj)
# MyClass(a=1,b=2,c=3)
obj2 = replace(obj, c=4.0)
print(obj2)
# MyClass(a=1,b=2,c=4.0)
replace
can also accept a second positional argument which is a dictionary
specifying a nested replacement. For example consider the following dict:
p = {"a": {"a1": 1, "a2": 2}, "b": {"b1": 3, "b2": 4}, "c": {"c1": 5, "c2": 6}}
With replace
the sub-dicts can be updated via:
replace(p, {"a": {"a1": 1.5}, "b": {"b2": 4.5}, "c": {"c1": 5.5}})
# {'a': {'a1': 1.5, 'a2': 2}, 'b': {'b1': 3, 'b2': 4.5}, 'c': {'c1': 5.5, 'c2': 6}}
In contrast in pure Python this would be:
from copy import deepcopy
newp = deepcopy(p)
newp["a"]["a1"] = 1.5
newp["b"]["b2"] = 4.5
newp["c"]["c1"] = 5.5
And this is the simplest case, where the mutability of a dict
allows us to copy the full object and update it after. Note that we had to use
deepcopy
to avoid
mutating the sub-dicts. So what if the objects are immutable?
@dataclass(frozen=True)
class Object:
x: float | dict
y: float
@dataclass(frozen=True)
class Collection:
a: Object
b: Object
p = Collection(Object(1.0, 2.0), Object(3.0, 4.0))
print(p)
Collection(a=Object(x=1.0, y=2.0), b=Object(x=3.0, y=4.0))
replace(p, {"a": {"x": 5.0}, "b": {"y": 6.0}})
# Collection(a=Object(x=5.0, y=2.0), b=Object(x=3.0, y=6.0))
With replace
this remains a one-liner. Replace pieces of any structure,
regardless of nesting.
To disambiguate dictionary fields from nested structures, use the F
marker.
from dataclassish import F
replace(p, {"a": {"x": F({"thing": 5.0})}})
# Collection(a=Object(x={'thing': 5.0}, y=2.0),
# b=Object(x=3.0, y=4.0))
Citation
If you found this library to be useful in academic work, then please cite.
Development
We welcome contributions!
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
Built Distribution
File details
Details for the file dataclassish-0.2.tar.gz
.
File metadata
- Download URL: dataclassish-0.2.tar.gz
- Upload date:
- Size: 14.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/5.1.0 CPython/3.12.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | b8a82cc69f33610a063d8ffd52f9b2dbd1fd20054c50f3ff36f01384253eaad5 |
|
MD5 | a3e82fe44a3c508963d65384d11b4c3b |
|
BLAKE2b-256 | 7d45f308d6678c37d5cee557967639ad09f3ecc8d642634998cdfd0c03889d49 |
File details
Details for the file dataclassish-0.2-py3-none-any.whl
.
File metadata
- Download URL: dataclassish-0.2-py3-none-any.whl
- Upload date:
- Size: 9.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/5.1.0 CPython/3.12.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 97935b963db5d87ec9ee8e223ca5b084a27192a669bb87f331aa16504571ccf2 |
|
MD5 | 886c21bc24a3d1af2b5e65751f17dc9f |
|
BLAKE2b-256 | e9703d4dcf6f2ec9bb7899595b328ab3134b5def2bda70ce9b3800d1827cdbc5 |