Skip to content

gh-116647: Fix recursive child in dataclasses #116790

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from Mar 19, 2024
Merged

gh-116647: Fix recursive child in dataclasses #116790

merged 7 commits into from Mar 19, 2024

Conversation

ghost
Copy link

@ghost ghost commented Mar 14, 2024

@ghost ghost requested a review from ericvsmith as a code owner March 14, 2024 09:35
@ghost
Copy link

ghost commented Mar 14, 2024

All commit authors signed the Contributor License Agreement.
CLA signed

@bedevere-app
Copy link

bedevere-app bot commented Mar 14, 2024

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@@ -1073,7 +1073,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
# Create __eq__ method. There's no need for a __ne__ method,
# since python will call __eq__ and negate it.
cmp_fields = (field for field in field_list if field.compare)
terms = [f'self.{field.name}==other.{field.name}' for field in cmp_fields]
terms = [f'(self.{field.name},)==(other.{field.name},)' for field in cmp_fields]
Copy link
Member

Choose a reason for hiding this comment

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

Is this an improvement over just reverting to the code before 18cfc1e?

Copy link
Author

Choose a reason for hiding this comment

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

I think this is just reverting to the code before 18cfc1e

due to (object_a, ) == (object_b) will first compare ids of objects, if it's not same then call eq of the object, that may not cause RecursionError: maximum recursion depth exceeded when the objects pointers are the same

but object_a == object_b directly calls object.eq whether the pointers is same or not, so i think remove 18cfc1e will solve the errors and provides better performance when the child have the same pointers will cause early return without calling eq of objects

Copy link
Member

Choose a reason for hiding this comment

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

My concern is that proposed code builds lots of tuples. The idea of 18cfc1e was to improve efficiency, and if the proposed code isn't at least as efficient as the pre-18cfc1e code, then it's a step backward.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the comment, I've improved the efficiency base on 18cfc1e now it'll compare id(obj) first to make sure if the child point to themselves will not cause maximum recursion depth and it'll be faster when compare exact same objects.

@ghost ghost requested a review from ericvsmith March 15, 2024 06:09
Copy link
Contributor

@Jason-Y-Z Jason-Y-Z left a comment

Choose a reason for hiding this comment

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

Overall LGTM, a small comment on the test case

@@ -2471,6 +2471,15 @@ def __repr__(self):


class TestEq(unittest.TestCase):
def test_recursive_eq(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the change! Per discussions in the issue, maybe also worth adding a test case for mutual recursive classes (given that's intended to be included in the scope of this PR)?

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the comment, but I don't think mutual recursive classes can be implement with good performance, I've think ways like maintenance dict like id(obj) as key to check if it's recursive but it'll cost spaces and times to make compare like that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Make sense, thanks for the clarification!

@ghost ghost requested a review from encukou March 19, 2024 01:42
@carljm carljm merged commit 7593574 into python:main Mar 19, 2024
vstinner pushed a commit to vstinner/cpython that referenced this pull request Mar 20, 2024
adorilson pushed a commit to adorilson/cpython that referenced this pull request Mar 25, 2024
diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants