Skip to content

gh-110222: Add support of PyStructSequence in copy.replace() #110223

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 30 commits into from
Oct 4, 2023

Conversation

XuehaiPan
Copy link
Contributor

@XuehaiPan XuehaiPan commented Oct 2, 2023

Resolves #110222

The implementation in this PR is roughly equivalent to:

class SomeStructSeq:
    def __replace__(self, /, **kwargs):
        if self.n_unamed_fields > 0:
            raise TypeError(f'__replace__() is not supported for {self.__class__} '
                            'because it has unnamed field(s)')

        field_names = self._get_field_names()
        _, (tup, dct) = self.__reduce__()
        seq = list(tup)
        for i, name in field_names[:self.n_sequence_fields]:
            if name in kwargs:
                seq[i] = kwargs.pop(name)
        for name in field_names[self.n_sequence_fields:]:
            if name in kwargs:
                dct[name] = kwargs.pop(name)
        if kwargs:
            raise TypeError(f'Got unexpected field name(s): {list(kwargs)!r}')
        return self.__class__(seq, dct)

Refactored:

class SomeStructSeq:
    def __replace__(self, /, **kwargs):
        if self.n_unamed_fields > 0:
            raise TypeError(f'__replace__() is not supported for {self.__class__} '
                            'because it has unnamed field(s)')

        clone = self._clone()
        field_names = self._get_field_names()
        if kwargs:
            for i, name in enumerate(field_names):
                if name in kwargs:
                    clone[i] = kwargs.pop(name)
            if kwargs:
                raise TypeError(f'Got unexpected field name(s): {list(kwargs)!r}')
        return clone

@serhiy-storchaka serhiy-storchaka self-requested a review October 2, 2023 16:04
@XuehaiPan XuehaiPan marked this pull request as ready for review October 2, 2023 16:59
@XuehaiPan XuehaiPan requested a review from rhettinger as a code owner October 2, 2023 16:59
Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

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

I am surprised to see a correct complex C code from a new contributor. It is not easy to write all these error checks and decrefs on the first version.

The implementation creates arguments (a tuple and a dict) for __new__() and calls it. It is not the most efficient way, because it creates intermediate tuple and dict and can create string keys for name-only fields multiple times, but it works, and performance is not critical. I'm just wondering, whether it was your first idea or you considered other approaches and they turned out more complex? Did you try to create a new instance with PyStructSequence_New(), then copy ob_items from old structure to the new, then iterate the kwargs dict in one pass and patch ob_items? It looks simpler to me, but I may have missed some details.

@XuehaiPan
Copy link
Contributor Author

I'm just wondering, whether it was your first idea or you considered other approaches and they turned out more complex? Did you try to create a new instance with PyStructSequence_New(), then copy ob_items from old structure to the new, then iterate the kwargs dict in one pass and patch ob_items? It looks simpler to me, but I may have missed some details.

@serhiy-storchaka My first thought is to create a copy of the current structseq and then replace the new values from kwargs. But I found it is kind of complex to handle the unnamed fields. Then I implement it with a similar structure to the __reduce__() implementation.

I will refactor it with the former implementation since we have disabled the support for types with unnamed fields.

@XuehaiPan
Copy link
Contributor Author

I will refactor it with the former implementation since we have disabled the support for types with unnamed fields.

Done.

@rhettinger rhettinger removed their request for review October 3, 2023 02:15
Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

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

Very well.

I have few more comments, mostly test suggestions.

@serhiy-storchaka
Copy link
Member

LGTM!

@serhiy-storchaka serhiy-storchaka merged commit 3bbe3b7 into python:main Oct 4, 2023
@serhiy-storchaka
Copy link
Member

Thank you for your contribution @XuehaiPan.

@XuehaiPan XuehaiPan deleted the copy-replace-structseq branch October 4, 2023 17:01
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.

Add support of PyStructSequence in copy.replace()
2 participants