Skip to content

Change TrialState.__repr__ and TrialState.__str__#6281

Merged
c-bata merged 7 commits intooptuna:masterfrom
ktns:trialstate.repr
Oct 17, 2025
Merged

Change TrialState.__repr__ and TrialState.__str__#6281
c-bata merged 7 commits intooptuna:masterfrom
ktns:trialstate.repr

Conversation

@ktns
Copy link
Copy Markdown
Contributor

@ktns ktns commented Sep 17, 2025

Motivation

According to this comment, TrialState.__repr__ was intended to return a string that can be passed to eval and evaluated as itself. At that time TrialState was a subclass of enum.Enum but now enum.IntEnum.__str__ returns just a numeric string.

Description of the changes

Use enum.Enum.__str__ in TrialState.__repr__ and ensure it's evaluated as itself via a unit test.

@nabenabe0928 nabenabe0928 changed the title TrialState.__repr__: return evaluable string Return evaluable string from TrialState.__repr__: Sep 19, 2025
@nabenabe0928 nabenabe0928 changed the title Return evaluable string from TrialState.__repr__: Return evaluable string from TrialState.__repr__ Sep 19, 2025
@nabenabe0928 nabenabe0928 added the compatibility Change that breaks compatibility. label Sep 25, 2025
@nabenabe0928
Copy link
Copy Markdown
Contributor

@c-bata
Could you review this PR?

@not522 not522 self-assigned this Sep 26, 2025
@not522
Copy link
Copy Markdown
Member

not522 commented Sep 29, 2025

Thank you for your PR and sorry for the late response.
IntEnum's __str__ and __repr__ were changed in Python 3.11 (https://docs.python.org/3.13/library/enum.html#enum.IntEnum). So, we need to discuss what behavior is desirable.
I think using Enum.__str__ for __str__ and keeping __repr__ as default is appropriate (https://docs.python.org/3.13/library/enum.html#notes).

class TrialState(enum.IntEnum):
    ...
    __str__ = enum.Enum.__str__
    ...
>>> import optuna
>>> str(optuna.trial.TrialState.COMPLETE)
'TrialState.COMPLETE'
>>> repr(optuna.trial.TrialState.COMPLETE)
'<TrialState.COMPLETE: 1>'

It does not return a valid Python expression for __repr__, but I think what number is used as the value is important for developers (See the reference https://docs.python.org/3.13/reference/datamodel.html#object.__repr__).
What do you think?

@c-bata c-bata removed their assignment Sep 29, 2025
@ktns
Copy link
Copy Markdown
Contributor Author

ktns commented Sep 30, 2025

I also prefer to use the default __repr__. When returning a valid Python expression isn't strictly required, let's use the default.

@not522
Copy link
Copy Markdown
Member

not522 commented Oct 1, 2025

Thank you! Could you update this PR as my comment?

@not522
Copy link
Copy Markdown
Member

not522 commented Oct 2, 2025

  1. How about adding __str__ = enum.Enum.__str__? In the current implementation, str(optuna.trial.TrialState.COMPLETE) just shows '1'.
  2. test_repr in test_frozen.py failed, but I think this test can be deleted.

@ktns
Copy link
Copy Markdown
Contributor Author

ktns commented Oct 3, 2025

strange, I pushed changes to the test but failing CI job seems to have executed the old test with eval...

@ktns
Copy link
Copy Markdown
Contributor Author

ktns commented Oct 3, 2025

Ah... sorry I misunderstood. I didn't notice there was another test using eval.

Copy link
Copy Markdown
Member

@not522 not522 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! LGTM!

@not522 not522 removed their assignment Oct 3, 2025
@ktns ktns changed the title Return evaluable string from TrialState.__repr__ Change TrialState.__repr__ and TrialState.__str__ Oct 3, 2025
@ktns
Copy link
Copy Markdown
Contributor Author

ktns commented Oct 3, 2025

I just noticed the PR title was obsolete. Is the new title OK?

@c-bata c-bata assigned gen740 and unassigned nabenabe0928 Oct 3, 2025
Copy link
Copy Markdown
Member

@gen740 gen740 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think tests/trial_tests/test_state.py is unnecessary since it only tests Python’s built-in functionality, not Optuna.

@ktns
Copy link
Copy Markdown
Contributor Author

ktns commented Oct 3, 2025

I think it's advisable to be there to notify people when stdlib changed behavior.

@ktns
Copy link
Copy Markdown
Contributor Author

ktns commented Oct 3, 2025

and also when the baseclass is changed again.

@gen740
Copy link
Copy Markdown
Member

gen740 commented Oct 10, 2025

I think it’s advisable to be there to notify people when stdlib changed behavior.

If so, the test should compare with the full output of the repr like,

@pytest.mark.parametrize("state", TrialState)
def test_trial_state_repr(state: TrialState) -> None:
    assert f"<TrialState.{state.name}: {state.value}>" in repr(state)

but I think even this is not necessary, because Optuna does not depend on any __repr__ output as far as I know.

Would there be any issue if the base class of TrialState changed and its __repr__ output became different?

@not522
Copy link
Copy Markdown
Member

not522 commented Oct 10, 2025

While I'm neutral about this test, Optuna's policy is to not test visual elements like visualization or error messages, so this test might be unnecessary.

@ktns
Copy link
Copy Markdown
Contributor Author

ktns commented Oct 16, 2025

I'd rather insist to keep the test because it describes the intention to use Enum.__repr__. Before I write this PR, I had totally no idea why TrialState.__repr__ is returning just a numeric string until finding this comment. PR comments are not easy to find.

@not522
Copy link
Copy Markdown
Member

not522 commented Oct 16, 2025

I'm neutral with having tests, but aren't your points about documentation or comments in the code rather than about the tests themselves?

@gen740
Copy link
Copy Markdown
Member

gen740 commented Oct 16, 2025

In the first place, why does TrialState use IntEnum?
IntEnum is intended for cases where the state needs to be interpreted as an integer—so that values can be added or subtracted.
However, TrialState is neither additive nor subtractive, so using a plain Enum would be more appropriate.

If Enum were used instead of IntEnum, the __repr__ method would not need to be overridden, and the corresponding test would be unnecessary.

@not522
Copy link
Copy Markdown
Member

not522 commented Oct 16, 2025

To explain the background: TrialState was originally Enum, but was changed to IntEnum during the implementation of JournalStorage (#3854). Inheriting from int makes the JSON conversion implementation within JournalStorage simpler.

@ktns
Copy link
Copy Markdown
Contributor Author

ktns commented Oct 16, 2025

Of course documentation or comments will do. I won't insist to have the test if they are written but I prefer test personally because documentation or comments tend to get ignored.

Do you want me to write comments?

@c-bata
Copy link
Copy Markdown
Member

c-bata commented Oct 16, 2025

@ktns Thank you for your continuous contribution!
Sorry for the extra work, but due to the recent update in the branch protection rule after #6302, could you please merge the latest master branch into yours? 🙏

Regarding the ongoing discussion, I also think leaving a code comment instead of unit tests would be more appropriate in this case, since it would be difficult to find the background from the current test code. I don’t think we should spend too much time for a few lines of test code, but my impression is closer to @gen740’s and @not522’s opinions.

@ktns
Copy link
Copy Markdown
Contributor Author

ktns commented Oct 16, 2025

Sorry, I totallly forgot that I added a comment on __str__ definition. Does this look good?

Copy link
Copy Markdown
Member

@c-bata c-bata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I totallly forgot that I added a comment on __str__ definition. Does this look good?

It looks good. Thank you for the update 🙏 LGTM.

@c-bata c-bata merged commit 3d4b9ce into optuna:master Oct 17, 2025
12 checks passed
@c-bata c-bata added this to the v4.6.0 milestone Oct 17, 2025
@gen740 gen740 removed their assignment Oct 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compatibility Change that breaks compatibility.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants