Thanks to visit codestin.com
Credit goes to github.com

Skip to content

FIX/API: do not reset backend key in rc_context #23299

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 4 commits into from
Jun 28, 2022

Conversation

tacaswell
Copy link
Member

@tacaswell tacaswell commented Jun 18, 2022

The motivating issue is that if the backend was the auto backend sentinel and
the backend was first fully resolved in an rc_context block, then we would
reset back to the sentinel an exit which would lead to strange behavior with
figures being erroneously closed.

Adding a general purpose exclusion list seems like a better option than special
casing either the backend key or the backend value.

Special case 'backend' key to not be reset.

Closes #23298

~If people like this idea I will write the tests and docs. ~

PR Summary

PR Checklist

Tests and Styling

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (install flake8-docstrings and run flake8 --docstring-convention=all).

Documentation

  • New features are documented, with examples if plot related.
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).

@tacaswell tacaswell added this to the v3.6.0 milestone Jun 18, 2022
@timhoffm
Copy link
Member

timhoffm commented Jun 18, 2022

I feel -0.5 on this. Do we really want to give users access to handling the backend state via a parameter to rc_context? That feels a bit out of place. Also, passing a user list of excludes is a foot gun because they can easily and unintendedly remove the backend exclusion.

More generally, the need for special-handling backend here (and also in #23263) indicates to ne that we have some design issue here:

I wonder whether we should track the backend state in rcParams at all. RcParams should only hold the defaults, not necessarily actual state. E.g. we store figure.figsize as a default, but the actual value is held by the figure instance. (That‘s not exactly comparable because we can have multiple figures but only one backend state. But say we could only have one figure, then we still would not store its size in rcParams. We shoul consider storing the actual backend in a central place outside of rcParams.

@jklymak
Copy link
Member

jklymak commented Jun 18, 2022

wonder whether we should track the backend state in rcParams at all. RcParams should only hold the defaults, not necessarily actual state. E.g. we store figure.figsize as a default, but the actual value is held by the figure instance.

👍 I don't think we should be storing state in rcParams. We probably could have a rule that nothing can set the rcParams inside the library so the user always has complete control over them.

@anntzer
Copy link
Contributor

anntzer commented Jun 18, 2022

I also don't think we should expose this API. While I also agree that storing the backend somewhere else than in rcParams may have been better, this may be slightly tricky to change (in particular, you need to keep the ability to set the backend in the matplotlibrc file, and I guess it would be better not to break people using rcParams["backend"] (even though get_backend() has existed as a replacement for a long time too)).

For the specific issue at hand, I think the problem is actually here:

    def __getitem__(self, key):
        if key in _deprecated_map:
            version, alt_key, alt_val, inverse_alt = _deprecated_map[key]
            _api.warn_deprecated(
                version, name=key, obj_type="rcparam", alternative=alt_key)
            return inverse_alt(dict.__getitem__(self, alt_key))

        elif key in _deprecated_ignore_map:
            version, alt_key = _deprecated_ignore_map[key]
            _api.warn_deprecated(
                version, name=key, obj_type="rcparam", alternative=alt_key)
            return dict.__getitem__(self, alt_key) if alt_key else None

        # In theory, this should only ever be used after the global rcParams
        # has been set up, but better be safe e.g. in presence of breakpoints.
        elif key == "backend" and self is globals().get("rcParams"):
            val = dict.__getitem__(self, key)
            if val is rcsetup._auto_backend_sentinel:
                from matplotlib import pyplot as plt
                plt.switch_backend(rcsetup._auto_backend_sentinel)

        return dict.__getitem__(self, key)
    def copy(self):
        rccopy = RcParams()
        for k in self:  # Skip deprecations and revalidation.
            dict.__setitem__(rccopy, k, dict.__getitem__(self, k))
        return rccopy

copy() (which is used by rc_context) uses dict.__getitem__ instead of RcParams.__getitem__ to skip deprecations, but this also skips backend-resolution. Instead, it should use an intermediate version of __getitem__ (_getitem_skipping_deprecations_but_resolving_backend). Now the copy won't see the sentinel. (This means that rc_context will trigger backend resolution, but I think that's actually reasonable: if you enter a rc_context you're about to do some plotting, which will typically need to resolve the backend anyways.)

@timhoffm
Copy link
Member

From the API point of view we want to have

  • delayed backend resolution. Ideally only when needed, i.e. not even at the start of the rc_context. But we could maybe bear the resolution at the start of rc_context if we can't get a better solution)
  • the end of the rc_conext must not reset the backend
  • no additional public API.

Every solution that fulfills this is a viable fix for #23298.

Whether/how we could separate the backend state from rcParams can be discussed separately.

@anntzer
Copy link
Contributor

anntzer commented Jun 18, 2022

Ah, I guess there's an important point that I missed here: direct assignment to rcParams["backend"] doesn't actually "work" (set the backend) once the backend has already been loaded by pyplot (perhaps this should error?), so I now think that rc_context should just not set that key at all either.

The motivating issue is that if the backend was the auto backend sentinel and
the backend was first fully resolved in an `rc_context` block, then we would
reset back to the sentinel an exit which would lead to strange behavior with
figures being erroneously closed.

This special cases the 'backend' key to not be reset on `__exit__`

Closes matplotlib#23298
@tacaswell tacaswell force-pushed the enh/excludelist-on_rc_context branch from 97fcbd6 to 7fce03e Compare June 22, 2022 03:26
@tacaswell tacaswell changed the title ENH/FIX: Allow rc_context to ignore keys on reset FIX/API: do not reset backend key in rc_context Jun 22, 2022
@tacaswell tacaswell marked this pull request as ready for review June 22, 2022 03:27
@tacaswell
Copy link
Member Author

Changed to simply drop the backend key when making the internal copy going with Tim and Antony's suggestion.

Co-authored-by: Tim Hoffmann <[email protected]>
@tacaswell
Copy link
Member Author

I mostly took your suggestion @timhoffm but re-worded it a little bit.

Copy link
Member

@QuLogic QuLogic left a comment

Choose a reason for hiding this comment

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

These should not be linked:

/home/circleci/project/doc/api/next_api_changes/behavior/23299-TAC.rst:1: WARNING: py:obj reference target not found: rcParams['backend']
/home/circleci/project/doc/api/next_api_changes/behavior/23299-TAC.rst:4: WARNING: py:obj reference target not found: rcParams['backend']
/home/circleci/project/doc/api/next_api_changes/behavior/23299-TAC.rst:4: WARNING: py:obj reference target not found: matpotlib.rc_context
/home/circleci/project/doc/api/next_api_changes/behavior/23299-TAC.rst:4: WARNING: py:obj reference target not found: rcParams['backend']

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

After addressing @QuLogic's style comments.

@tacaswell tacaswell force-pushed the enh/excludelist-on_rc_context branch from ddc50d6 to 69babbd Compare June 26, 2022 02:48
@tacaswell tacaswell force-pushed the enh/excludelist-on_rc_context branch from 69babbd to 545fff0 Compare June 26, 2022 02:49
@tacaswell
Copy link
Member Author

I went with the :rc: markup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants