Skip to content

universal copy-and-modify function - #15750

Closed
nstarman wants to merge 1 commit into
astropy:mainfrom
nstarman:universal-replace
Closed

nstarman wants to merge 1 commit into
astropy:mainfrom
nstarman:universal-replace

Conversation

@nstarman

@nstarman nstarman commented Dec 15, 2023 •

Copy link
Copy Markdown
Member

TODO:

  • tests
  • changelog & what's new
  • documentation
  • implement this in a whole bunch more places

As came up in discussion in #15643, it would be very convenient to have a consistent means to copy an instance, replacing a certain set of attributes. In Cosmology there's clone, in BaseCoordinateFrame there's replicate.
This PR doesn't replace these methods (which should still be made consistent), rather introducing a new universal framework styled off of the dataclasses function replace. With replace any Astropy object can easily register an implementation for copy-and-modify!

>>> from astropy.utils import replace
>>> replace(skycoord, new_attr=...)
>>> replace(quantity, unit=...)
...

How does this work? functools.singledispatch means one function (replace) is defined in the module astropy.utils.replace. Normally you would need a big if-else list to handle different data types. Or roll a custom registration system. functools.singledispatch takes care of the registration for us and is well documented in core python, so this is an easy system to teach and is a very stable solution. All you need to do register support for a new type is

from astropy.utils import replace

@replace.register
def _register_mytype(obj: MyType, /, **kwargs: Any) -> MyType:
    ...

functools.singledispatch introspects the function for the annotations and handles everything. replace.register can also accept arguments directly. See functools for details.

P.S. functools.singledispatch also handles subclasses very well.
P.P.S. I registered dataclasses.replace into astropy.utils.replace, so ALL dataclasses are supported!

Long-term note:
If this PR is adopted I actually think we should plan to spin it off into a micro-package that becomes a runtime dependency for Astropy. It's such a simple (and IMO elegant) solution to the copy-and-modify issue that I think this will be genuinely useful far outside of Astropy, at the very least in coordinated and affiliate packages. Since it's just a simple wrapper around a core-python future, this will be a very stable dependency. But we can discuss this plan after this PR.

  • By checking this box, the PR author has requested that maintainers do NOT use the "Squash and Merge" button. Maintainers should respect this when possible; however, the final decision is at the discretion of the maintainer that merges the PR.

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • Is a milestone set? Milestone must be set but we cannot check for it on Actions; do not let the green checkmark fool you.
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Thank you for your draft pull request! Do you know that you can use [ci skip] or [skip ci] in your commit messages to skip running continuous integration tests until you are ready?

Comment thread astropy/units/quantity.py Outdated


@replace.register
def _replace_quantity(obj: _QuantityT, /, **kwargs: Any) -> _QuantityT:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This handles Quantity subclasses, but we might want some more specific functions that better address nuances.

Styled off of the dataclasses function.

Signed-off-by: nstarman <[email protected]>
@nstarman

Copy link
Copy Markdown
Member Author

Ping @ayshih (as a note, you're not in the any-can-join public Astropy Members group so I can't request a review)

@nstarman nstarman changed the title universal replace function universal copy-and-modify function Dec 15, 2023
Comment thread astropy/units/quantity.py
Comment on lines +2245 to +2246
unit = kwargs.pop("unit", obj.unit)
return type(obj)(value, unit=unit, **kwargs)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Suggested change
unit = kwargs.pop("unit", obj.unit)
return type(obj)(value, unit=unit, **kwargs)
kwargs.setdefault("unit", obj.unit)
return type(obj)(value, **kwargs)

@pllim pllim added this to the v6.1.0 milestone Dec 15, 2023
@pllim

pllim commented Dec 15, 2023

Copy link
Copy Markdown
Member

Re: group membership -- The automation that Matt Craig set up a while ago is currently broken. But I have manually invited ayshih to astropy-contributors group. Hopefully he accepts!

@ayshih

ayshih commented Dec 18, 2023

Copy link
Copy Markdown
Contributor

Re this PR: It's an interesting approach! I think the challenge would be to educate users that this function even exists, since I imagine that the natural inclination would continue to be to hunt for the appropriate class-specific method. It doesn't help that astropy.utils is currently more for developers than for non-developer users, so I don't know if the latter group would even think to look there.

Re adding me to astropy-contributors: Thanks! (I was curious, so I looked up when I first contributed to Astropy, and it was in September 2015.)

@pllim
pllim requested a review from ayshih December 18, 2023 15:19
@nstarman

Copy link
Copy Markdown
Member Author

I think the challenge would be to educate users that this function even exists, since I imagine that the natural inclination would continue to be to hunt for the appropriate class-specific method.

The docs would be the most natural place, I suppose. Most classes in Astropy don't have a class-specific method, so hunting will only turn up replace :)

It doesn't help that astropy.utils is currently more for developers than for non-developer users, so I don't know if the latter group would even think to look there.

Agreed. This is a larger problem we have. We need to better distinguish utils for users, utils for affiliate package developers, and internal utils.

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I left a few in-line comments, but like @ayshih, I worry about visibility for these kinds of small "handy" functions. Indeed, it may not see that much use even in astropy (less than 10 true uses of .replicate, mostly in SpectralCoordinate and Time). Given those cases, I think there will be very little outside use (as far as I can tell from a quick check, I do not ever use .replicate myself). It seems to me it really mostly makes sense in a modeling situation (this including Cosmology), where it is meaningful to just replace one attribute.

Overall, maybe just try to make cosmology and modeling have a consistent interface?

"""
# Currently ``replicate`` is not implemented for SkyCoord and returns a
# ``BaseCoordinateFrame``object.
return obj.__class__(obj.replicate(copy=copy, **kwargs))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Realize this is still a draft just to see whether we want it at all, but this won't be quite right, since all non-frame attributes get lost.

Comment thread astropy/utils/replace.py
@@ -0,0 +1,46 @@
"""
A simple class to manage a piece of global science state. See

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

copy & paste error?

@nstarman

Copy link
Copy Markdown
Member Author

I guess this would be better handled as an external library. It's a generically useful solution to a common Python problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants