Skip to content

Cleanup _pylab_helpers. #13581

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 1 commit into from
Sep 18, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 46 additions & 58 deletions lib/matplotlib/_pylab_helpers.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,41 @@
"""
Manage figures for pyplot interface.
Manage figures for the pyplot interface.
"""

import atexit
from collections import OrderedDict
import gc


class Gcf:
"""
Singleton to manage a set of integer-numbered figures.
Singleton to maintain the relation between figures and their managers, and
keep track of and "active" figure and manager.

This class is never instantiated; it consists of two class
attributes (a list and a dictionary), and a set of static
methods that operate on those attributes, accessing them
directly as class attributes.
The canvas of a figure created through pyplot is associated with a figure
manager, which handles the interaction between the figure and the backend.
pyplot keeps track of figure managers using an identifier, the "figure
number" or "manager number" (which can actually be any hashable value);
this number is available as the :attr:`number` attribute of the manager.

This class is never instantiated; it consists of an `OrderedDict` mapping
figure/manager numbers to managers, and a set of class methods that
manipulate this `OrderedDict`.

Attributes
----------
figs
dictionary of the form {*num*: *manager*, ...}
_activeQue
list of *managers*, with active one at the end

figs : OrderedDict
`OrderedDict` mapping numbers to managers; the active manager is at the
end.
"""
_activeQue = []
figs = {}

figs = OrderedDict()

@classmethod
def get_fig_manager(cls, num):
"""
If figure manager *num* exists, make it the active
figure and return the manager; otherwise return *None*.
If manager number *num* exists, make it the active one and return it;
otherwise return *None*.
"""
manager = cls.figs.get(num, None)
if manager is not None:
Expand All @@ -40,90 +45,73 @@ def get_fig_manager(cls, num):
@classmethod
def destroy(cls, num):
"""
Try to remove all traces of figure *num*.
Destroy figure number *num*.

In the interactive backends, this is bound to the
window "destroy" and "delete" events.
In the interactive backends, this is bound to the window "destroy" and
"delete" events.
"""
if not cls.has_fignum(num):
return
manager = cls.figs[num]
manager = cls.figs.pop(num)
manager.canvas.mpl_disconnect(manager._cidgcf)
cls._activeQue.remove(manager)
del cls.figs[num]
manager.destroy()
gc.collect(1)

@classmethod
def destroy_fig(cls, fig):
"*fig* is a Figure instance"
num = next((manager.num for manager in cls.figs.values()
if manager.canvas.figure == fig), None)
if num is not None:
cls.destroy(num)
"""Destroy figure *fig*."""
canvas = getattr(fig, "canvas", None)
manager = getattr(canvas, "manager", None)
num = getattr(manager, "num", None)
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 always equivalent, since it seems to be working in reverse?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This basically gets fig.canvas.manager.num with the provision that each of these can be None. If this num is not registered in the figs dict, destroy will catch that as it first checks cls.has_fignum(num).
It's true that this may cause problems if two managers share the same num, but then the num->manager mapping is already invalid (set_active will already have overwritten the entry in the dict) so I'm not too worried about that.

cls.destroy(num)

@classmethod
def destroy_all(cls):
# this is need to ensure that gc is available in corner cases
# where modules are being torn down after install with easy_install
import gc # noqa
"""Destroy all figures."""
# Reimport gc in case the module globals have already been removed
# during interpreter shutdown.
import gc
for manager in list(cls.figs.values()):
manager.canvas.mpl_disconnect(manager._cidgcf)
manager.destroy()

cls._activeQue = []
cls.figs.clear()
gc.collect(1)

@classmethod
def has_fignum(cls, num):
"""
Return *True* if figure *num* exists.
"""
"""Return whether figure number *num* exists."""
return num in cls.figs

@classmethod
def get_all_fig_managers(cls):
"""
Return a list of figure managers.
"""
"""Return a list of figure managers."""
return list(cls.figs.values())

@classmethod
def get_num_fig_managers(cls):
"""
Return the number of figures being managed.
"""
"""Return the number of figures being managed."""
return len(cls.figs)

@classmethod
def get_active(cls):
"""
Return the manager of the active figure, or *None*.
"""
if len(cls._activeQue) == 0:
return None
else:
return cls._activeQue[-1]
"""Return the active manager, or *None* if there is no manager."""
return next(reversed(cls.figs.values())) if cls.figs else None

@classmethod
def set_active(cls, manager):
"""
Make the figure corresponding to *manager* the active one.
"""
oldQue = cls._activeQue[:]
cls._activeQue = [m for m in oldQue if m != manager]
cls._activeQue.append(manager)
"""Make *manager* the active manager."""
cls.figs[manager.num] = manager
cls.figs.move_to_end(manager.num)

@classmethod
def draw_all(cls, force=False):
"""
Redraw all figures registered with the pyplot
state machine.
Redraw all stale managed figures, or, if *force* is True, all managed
figures.
"""
for f_mgr in cls.get_all_fig_managers():
if force or f_mgr.canvas.figure.stale:
f_mgr.canvas.draw_idle()
for manager in cls.get_all_fig_managers():
if force or manager.canvas.figure.stale:
manager.canvas.draw_idle()


atexit.register(Gcf.destroy_all)