Skip to content
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

[Bug]: get_backend() clears figures from Gcf.figs if they were created under rc_context #23298

Closed
drammock opened this issue Jun 17, 2022 · 9 comments · Fixed by #23299
Closed
Milestone

Comments

@drammock
Copy link

drammock commented Jun 17, 2022

Bug summary

calling matplotlib.get_backend() removes all figures from Gcf if the first figure in Gcf.figs was created in an rc_context.

Code for reproduction

import matplotlib.pyplot as plt
from matplotlib import get_backend, rc_context

# fig1 = plt.figure()  # <- UNCOMMENT THIS LINE AND IT WILL WORK
# plt.ion()            # <- ALTERNATIVELY, UNCOMMENT THIS LINE AND IT WILL ALSO WORK
with rc_context():
    fig2 = plt.figure()
before = f'{id(plt._pylab_helpers.Gcf)} {plt._pylab_helpers.Gcf.figs!r}'
get_backend()
after = f'{id(plt._pylab_helpers.Gcf)} {plt._pylab_helpers.Gcf.figs!r}'

assert before == after, '\n' + before + '\n' + after

Actual outcome

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-1-fa4d099aa289> in <cell line: 11>()
      9 after = f'{id(plt._pylab_helpers.Gcf)} {plt._pylab_helpers.Gcf.figs!r}'
     10 
---> 11 assert before == after, '\n' + before + '\n' + after
     12 

AssertionError: 
94453354309744 OrderedDict([(1, <matplotlib.backends.backend_qt.FigureManagerQT object at 0x7fb33e26c220>)])
94453354309744 OrderedDict()

Expected outcome

The figure should not be missing from Gcf. Consequences of this are, e.g, plt.close(fig2) doesn't work because Gcf.destroy_fig() can't find it.

Additional information

No response

Operating system

Xubuntu

Matplotlib Version

3.5.2

Matplotlib Backend

QtAgg

Python version

Python 3.10.4

Jupyter version

n/a

Installation

conda

@tacaswell
Copy link
Member

My knee-jerk guess is that :

  • the rcParams['backend'] in the auto-sentinel
  • that is stashed by rc_context
  • if you do the first thing to force the backend to be resolved in the context manager it get changes
  • the context manager sets it back to the sentinel an the way out
  • get_backend() re-resolves the backend which because it changes the backend it closes all of the figures

This is probably been a long standing latent bug, but was brought to the front when we made the backend resolution lazier.

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

That is indeed the problem. I do not see an easy way to fix this that we can backport to 3.5.x, PR coming with a way to fix this for 3.6....

tacaswell added a commit to tacaswell/matplotlib that referenced this issue 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.

Closes matplotlib#23298
tacaswell added a commit to tacaswell/matplotlib that referenced this issue Jun 22, 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.

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

Closes matplotlib#23298
@kailizcatman
Copy link

I encountered this bug when using matplotlib (3.5.2) indirectly via seaborn. Before my environment is updated with matplotlib 3.6, what would you recommend as a workaround for this bug? The workaround should preferably have as little side effect as possible.

@tacaswell
Copy link
Member

Adding

import matplotlib
matplotlib.get_backend()
del matplotilb 

should force the backend resolution to work around this issue and have no other side effects. This only needs to be done once per process.

@kailizcatman
Copy link

Thank you.

@jklymak
Copy link
Member

jklymak commented Jul 24, 2022

But also seaborn need not use a context manager here?

@mwaskom
Copy link

mwaskom commented Jul 30, 2022

But also seaborn need not use a context manager here?

?

@jklymak
Copy link
Member

jklymak commented Jul 30, 2022

I think seaborn can turn the layout off (fig.set_tight_layout(False)). I guess if you want it to turn back on, you could save the old state and toggle it back on.

I guess philosophically I don't think of rcParams as something downstream libraries should be changing, but rather defaults that the user sets. However I appreciate that there is a counter current in the project of thinking of rcParams as a global state for the library that gets manipulated at will, and I can see how the context manager helps encourage that. However, I'm just not convinced it is a good idea given the number of weird side effects it can produce.

@mwaskom
Copy link

mwaskom commented Jul 30, 2022

The rcParams very much are "a global state that can be manipulated at will" so if doing so can produce "weird side effects", it seems important for matplotlib to clearly document them.

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 a pull request may close this issue.

5 participants