Skip to main content

dataclassish

Tools from dataclasses, extended to all of Python

PyPI: dataclassish PyPI versions: dataclassish dataclassish license

CI status codecov ruff ruff pre-commit


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

PyPI platforms PyPI version

pip install dataclassish

Documentation

Getting Started

Replacing a @dataclass

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(frozen=True)
... class Point:
...     x: float | int
...     y: float | int


>>> p = Point(1.0, 2.0)
>>> p
Point(x=1.0, y=2.0)

>>> p2 = replace(p, x=3.0)
>>> p2
Point(x=3.0, y=2.0)

Replacing a dict

Now we'll work with a dict object. Note that dataclasses does not work with dict objects, but with dataclassish it's easy!

>>> from dataclassish import replace

>>> p = {"x": 1, "y": 2.0}
>>> p
{'x': 1, 'y': 2.0}

>>> p2 = replace(p, x=3.0)
>>> 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'}.

Replacing via the __replace__ Method

In Python 3.13+ objects can implement the __replace__ method to define how copy.replace should operate on them. This was directly inspired by dataclass.replace, and is a nice generalization to more general Python objects. dataclassish too supports this method.

>>> class HasReplace:
...     def __init__(self, a, b):
...         self.a = a
...         self.b = b
...     def __repr__(self) -> str:
...         return f"HasReplace(a={self.a},b={self.b})"
...     def __replace__(self, **changes):
...         return type(self)(**(self.__dict__ | changes))

>>> obj = HasReplace(1, 2)
>>> obj
HasReplace(a=1,b=2)

>>> obj2 = replace(obj, b=3)
>>> obj2
HasReplace(a=1,b=3)

Replacing a Custom Type

Let's say there's a custom object that we want to use replace on, but which doesn't have a __replace__ method (or which we want more control over using a second argument, discussed later). 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)
>>> obj
MyClass(a=1,b=2,c=3)

>>> obj2 = replace(obj, c=4.0)
>>> obj2
MyClass(a=1,b=2,c=4.0)

Nested Replacement

replace can also accept a second positional argument which is a dictionary specifying a nested replacement. For example consider the following dict of Point objects:

>>> p = {"a": Point(1, 2), "b": Point(3, 4), "c": Point(5, 6)}

With replace the nested structure can be updated via:

>>> replace(p, {"a": {"x": 1.5}, "b": {"y": 4.5}, "c": {"x": 5.5}})
{'a': Point(x=1.5, y=2), 'b': Point(x=3, y=4.5), 'c': Point(x=5.5, y=6)}

In contrast in pure Python this would be very challenging. Expand the example below to see how this might be done.

Expand for detailed example

This is a bad approach, updating the frozen dataclasses in place:

>>> from copy import deepcopy

>>> newp = deepcopy(p)
>>> object.__setattr__(newp["a"], "x", 1.5)
>>> object.__setattr__(newp["b"], "y", 4.5)
>>> object.__setattr__(newp["c"], "x", 5.5)

A better way might be to create an entirely new object!

>>> newp = {"a": Point(1.5, p["a"].y),
...         "b": Point(p["b"].x, 4.5),
...         "c": Point(5.5, p["c"].y)}

This isn't so good either.

dataclassish.replace is a one-liner that can work on any object (if it has a registered means to do so), regardless of mutability or nesting. Consider this fully immutable structure:

>>> @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))
>>> 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))

dataclass tools

dataclasses has a number of utility functions beyond replace: fields, asdict, and astuple. dataclassish supports of all these functions.

>>> from dataclassish import fields, asdict, astuple

>>> p = Point(1.0, 2.0)

>>> fields(p)
(Field(name='x',...), Field(name='y',...))

>>> asdict(p)
{'x': 1.0, 'y': 2.0}

>>> astuple(p)
(1.0, 2.0)

dataclassish extends these functions to dict's:

>>> p = {"x": 1, "y": 2.0}

>>> fields(p)
(Field(name='x',...), Field(name='y',...))

>>> asdict(p)
{'x': 1, 'y': 2.0}

>>> astuple(p)
(1, 2.0)

Support for custom objects can be implemented similarly to replace.

More tools

In addition to the dataclasses tools, dataclassish provides a few more utilities.

  • get_field returns the field of an object by name.
  • field_keys returns the names of an object's fields.
  • field_values returns the values of an object's fields.
  • field_items returns the names and values of an object's fields.
>>> from dataclassish import get_field, field_keys, field_values, field_items

>>> p = Point(1.0, 2.0)

>>> get_field(p, "x")
1.0

>>> field_keys(p)
('x', 'y')

>>> field_values(p)
(1.0, 2.0)

>>> field_items(p)
(('x', 1.0), ('y', 2.0))

These functions work on any object that has been registered in, not just @dataclass objects.

>>> p = {"x": 1, "y": 2.0}

>>> get_field(p, "x")
1

>>> field_keys(p)
dict_keys(['x', 'y'])

>>> field_values(p)
dict_values([1, 2.0])

>>> field_items(p)
dict_items([('x', 1), ('y', 2.0)])

Converters

While dataclasses.field itself does not allow for converters (See PEP 712) many dataclasses-like libraries do. A very short, very non-exhaustive list includes: attrs and equinox. The module dataclassish.converters provides a few useful converter functions. If you need more, check out attrs!

>>> from attrs import define, field
>>> from dataclassish.converters import Optional, Unless


>>> @define
... class Class1:
...     attr: int | None = field(default=None, converter=Optional(int))
...     """attr is converted to an int or kept as None."""


>>> obj = Class1()
>>> print(obj.attr)
None

>>> obj = Class1(attr=1.0)
>>> obj.attr
1

>>> @define
... class Class2:
...     attr: float | int = field(converter=Unless(int, converter=float))
...     """attr is converted to a float, unless it's an int."""

>>> obj = Class2(1)
>>> obj.attr
1

>>> obj = Class2("1")
>>> obj.attr
1.0

This library also provide a lightweight dataclass-like decorator and field function that supports these converters and converters in general.

>>> from dataclassish.converters import dataclass, field

>>> @dataclass
... class MyClass:
...     a: int | None = field(converter=Optional(int))
...     b: str = field(converter=str.upper)

>>> obj = MyClass(a="1", b="hello")
>>> obj
MyClass(a=1, b='HELLO')

>>> obj = MyClass(a=None, b="there")
>>> obj
MyClass(a=None, b='THERE')

Flags

dataclassish provides flags for customizing the behavior of functions. For example, the coordinax package, which depends on dataclassish, uses a flag AttrFilter to filter out fields from consideration by the functions in dataclassish.

dataclassish provides a few built-in flags and flag-related utilities.

>>> from dataclassish import flags
>>> flags.__all__
('FlagConstructionError', 'AbstractFlag', 'NoFlag', 'FilterRepr')

Where AbstractFlag is the base class for flags, NoFlag is a flag that does nothing, and FilterRepr will filter out any fields with repr=True. FlagConstructionError is an error that is raised when a flag is constructed incorrectly.

As a quick example, we'll show how to use NoFlag.

>>> from dataclassish import field_keys
>>> tuple(field_keys(flags.NoFlag, p))
('x', 'y')

As another example, we'll show how to use FilterRepr.

>>> from dataclasses import field
>>> @dataclass
... class Point:
...     x: float
...     y: float = field(repr=False)
>>> obj = Point(1.0, 2.0)

>>> field_keys(flags.FilterRepr, obj)
('x',)

Citation

DOI

If you enjoyed using this library and would like to cite the software you use then click the link above.

Development

Actions Status codecov SPEC 0 — Minimum Supported Dependencies pre-commit ruff

We welcome contributions!

Metadata

Release files for dataclassish 0.9.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dataclassish 0.9.0
File Size Uploaded
dataclassish-0.9.0.tar.gz 73.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dataclassish 0.9.0
File Interpreter ABI Platform
dataclassish-0.9.0-py3-none-any.whl Python 3 none any Details

Total release size: 97.7 kB

Release files / dataclassish-0.9.0.tar.gz

Download URL dataclassish-0.9.0.tar.gz
Size 73.2 kB
Tags Source
SHA-256 checksum
How to use checksums
310b0d6d00442ca93dbc9b3742a91e25e157582d10fd3e9e453d35767f5d9347
BLAKE2b-256 checksum
How to use checksums
dc67835a8f06347fe630a030fa079028b1984ad81b7369b31dfbbfd4e2877750
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.

Transparency log

Release files / dataclassish-0.9.0-py3-none-any.whl

Download URL dataclassish-0.9.0-py3-none-any.whl
Size 24.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2475cdfed689f2b0fced119f9cb5af8a200fc1b48c7afd25a7296c4a8cfbfe90
BLAKE2b-256 checksum
How to use checksums
3720d735e30dd693fe32cad69d13889344c1132b1871f508c49bda04880df8f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.9.0 This release

2 release files

0.8.0

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page