pythonpython-dataclassesrepr

Exclude default fields from python `dataclass` `__repr__`


Summary

I have a dataclass with 10+ fields. print()ing them buries interesting context in a wall of defaults - let's make them friendlier by not needlessly repeating those.

Dataclasses in Python

Python's @dataclasses.dataclass() (PEP 557) provides automatic printable representations (__repr__()).

Assume this example, based on python.org's:

from dataclasses import dataclass


@dataclass
class InventoryItem:
    name: str
    unit_price: float = 1.00
    quantity_on_hand: int = 0

The decorator, through @dataclass(repr=True) (default) will print() a nice output:

InventoryItem(name='Apple', unit_price='1.00', quantity_on_hand=0)

What I want: Skip printing the defaults

repr It prints all the fields, including implied defaults you wouldn't want to show.

print(InventoryItem("Apple"))

# Outputs: InventoryItem(name='Apple', unit_price='1.00', quantity_on_hand=0)
# I want: InventoryItem(name='Apple')
print(InventoryItem("Apple", unit_price="1.05"))

# Outputs: InventoryItem(name='Apple', unit_price='1.05', quantity_on_hand=0)
# I want: InventoryItem(name='Apple', unit_price='1.05')
print(InventoryItem("Apple", quantity_on_hand=3))

# Outputs: InventoryItem(name='Apple', unit_price=1.00, quantity_on_hand=3)
# I want: InventoryItem(name='Apple', quantity_on_hand=3)
print(InventoryItem("Apple", unit_price='2.10', quantity_on_hand=3))

# Output is fine (everything's custom):
# InventoryItem(name='Apple', unit_price=2.10, quantity_on_hand=3)

Discussion

Internally, here's the machinery of dataclass repr-generator as of python 3.10.4: cls.__repr__=_repr_fn(flds, globals)) -> _recursive_repr(fn)

It may be the case that @dataclass(repr=False) be switched off and def __repr__(self): be added.

If so, what would that look like? We don't want to include the optional defaults.

Context

To repeat, in practice, my dataclass has 10+ fields.

I'm print()ing instances via running the code and repl, and @pytest.mark.parametrize when running pytest with -vvv.

Big dataclass' non-defaults (sometimes the inputs) are impossible to see as they're buried in the default fields and worse, each one is disproportionately and distractingly huge: obscuring other valuable stuff bring printed.

Related questions

As of today there aren't many dataclass questions yet (this may change):


Solution

  • You could do it like this:

    import dataclasses
    from dataclasses import dataclass
    from operator import attrgetter
    
    
    @dataclass(repr=False)
    class InventoryItem:
        name: str
        unit_price: float = 1.00
        quantity_on_hand: int = 0
    
        def __repr__(self):
            nodef_f_vals = (
                (f.name, attrgetter(f.name)(self))
                for f in dataclasses.fields(self)
                if attrgetter(f.name)(self) != f.default
            )
    
            nodef_f_repr = ", ".join(f"{name}={value}" for name, value in nodef_f_vals)
            return f"{self.__class__.__name__}({nodef_f_repr})"
            
    
    # Prints: InventoryItem(name=Apple)
    print(InventoryItem("Apple"))
    
    # Prints: InventoryItem(name=Apple,unit_price=1.05)
    print(InventoryItem("Apple", unit_price="1.05"))
    
    # Prints: InventoryItem(name=Apple,unit_price=2.10,quantity_on_hand=3)
    print(InventoryItem("Apple", unit_price='2.10', quantity_on_hand=3))