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.2

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.2
File Size Uploaded
dataclassish-0.9.2.tar.gz 74.3 kB Details

Built distribution (wheel)

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

Total release size: 99.0 kB

Release files / dataclassish-0.9.2.tar.gz

Download URL dataclassish-0.9.2.tar.gz
Size 74.3 kB
Tags Source
SHA-256 checksum
How to use checksums
17914e32fade0ae5c5966f7983051e3abf14cc25b45a20027862cb9bc644e71c
BLAKE2b-256 checksum
How to use checksums
fe9d4fb14c319559b19197d3f489e000f674e7936ee8c9cf4af82e1a3cd4df4b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 11, 2026.

Transparency log

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

Download URL dataclassish-0.9.2-py3-none-any.whl
Size 24.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
46040c5ed19a11fec76e9c849911d84f9279f96e3ff3d2535e7ca7d635cf5fad
BLAKE2b-256 checksum
How to use checksums
3da34679cc315e170e4ef57a8f1cbc9a48066b6e568a6b11c0c45c0215b63d45
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.9.2 This release

2 release files

0.9.1

2 release files

0.9.0

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