python-attrs / attrs

Python Classes Without Boilerplate
https://www.attrs.org/
MIT License
5.31k stars 374 forks source link

Provide option in attrs.define to allow users to exclude parameters set to default value from `repr` #1276

Open RNKuhns opened 7 months ago

RNKuhns commented 7 months ago

Summary

This PR provides functionality in line with #1193.

It adds a new parameter to attrs.define to allow users to toggle on/off the ability to create classes that dynamically generate their repr to include only parameters set to values other than their default. The added parameter is set to a default value that maintains the existing functionality (always have static repr).

The functionality is designed to work such as follows:

@attrs.define(kw_only=True, only_non_default_attr_in_repr=True)
class SomeClass:

    something: int | None = attrs.field(default=None, repr=True)
    something_else: bool = attrs.field(default=False, repr=True)
    another: float | None = attrs.field(default=11.0, init=False, repr=False)

some_class = SomeClass(something=7)
SomeClass(something=7)

# If we wanted to exclude the something param from the repr in the field def we still can
@attrs.define(kw_only=True, only_non_default_attr_in_repr=True)
class SomeClass:

    something: int | None = attrs.field(default=None, repr=False)
    something_else: bool = attrs.field(default=False, repr=True)
    another: float | None = attrs.field(default=11.0, init=False, repr=False)

some_class = SomeClass(something=7)
SomeClass()

# The default is equivalent of only_non_default_attr_in_repr=False, so existing behavior is default
@attrs.define(kw_only=True)
class SomeClass:

    something: int | None = attrs.field(default=None)
    something_else: bool = attrs.field(default=False)
    another: float | None = attrs.field(default=11.0, init=False, repr=False)

some_class = SomeClass(something=7)
SomeClass(something=7, something_else=False)

# The use of repr in a field works just like it did before so we can exclude individual attributes
# from the repr (or pass custom callable)
@attrs.define(kw_only=True)
class SomeClass:

    something: int | None = attrs.field(default=None, repr=False)
    something_else: bool = attrs.field(default=False)
    another: float | None = attrs.field(default=11.0, init=False)

some_class = SomeClass(something=7)
SomeClass(something_else=False, another=11.0)

@hynek I'll look into creating some simple test cases (like above examples) to test cases for this. But I'd appreciate some feedback on whether this approach makes sense to you before I finish up with that.

Note there might be some nuisance edits to the _make.py that ruff made when I saved the file in my setup, but these should be minor.

Pull Request Check List

RNKuhns commented 7 months ago

Note that I'll also add examples to the docs before this is finalized/merged if the concept moves forward.

RNKuhns commented 7 months ago

@hynek I know you are probably busy, but I just wanted to check in to see if you had a chance to take a look and provide feedback.

hynek commented 7 months ago

yeah sorry I'm swamped right now, as you can tell in my own PR #1267 that hasn't moved in a while. I don't have the headspace for bigger changes right now, but it won't get lost as long as you leave it open. I hope to be able to clean up the trackers before leaving for PyCon US (2024 – just in case ;))

RNKuhns commented 7 months ago

yeah sorry I'm swamped right now, as you can tell in my own PR #1267 that hasn't moved in a while. I don't have the headspace for bigger changes right now, but it won't get lost as long as you leave it open. I hope to be able to clean up the trackers before leaving for PyCon US (2024 – just in case ;))

No problem at all! I'll leave it open and add some of the finishing touches (tests cases, docs) as I have time on the next week or two.

RNKuhns commented 5 months ago

@hynek I've updated this to handle default factories and include a description of the functionality in the attrs by examples section of the documentation.

Let me know when you have a chance to take a look.

codspeed-hq[bot] commented 4 months ago

CodSpeed Performance Report

Merging #1276 will degrade performances by 28.49%

Comparing RNKuhns:repr_optionally_exclude_param_defaults (9f1963b) with main (f5683b8)

Summary

❌ 3 regressions ✅ 5 untouched benchmarks

:warning: _Please fix the performance issues or acknowledge them on CodSpeed._

Benchmarks breakdown

Benchmark main RNKuhns:repr_optionally_exclude_param_defaults Change
test_create_frozen_class 1.7 s 2.3 s -25.32%
test_create_simple_class 1.5 s 2.1 s -28.06%
test_create_simple_class_make_class 1.5 s 2 s -28.49%
hynek commented 4 months ago

Hey sorry once again, for all the delays. :( Before starting investigating, do you have any idea why this PR slows down class creation (not instantiation) by almost 30%? That's quite a lot and sadly a rather critical benchmark in the whole space of creating classes. :|

RNKuhns commented 3 months ago

@hynek I do plan to circle back on this. I've just got a couple things going on at the moment.

RNKuhns commented 3 months ago

Hey sorry once again, for all the delays. :( Before starting investigating, do you have any idea why this PR slows down class creation (not instantiation) by almost 30%? That's quite a lot and sadly a rather critical benchmark in the whole space of creating classes. :|

Not a problem -- I'll take a look at this and work through what is driving those performance degradations.