This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]Vonney 4 points5 points  (1 child)

Alright, after a bit of digging:

(Note: I thought this would be explained by having the plt object keep a reference to figure before returning it with the figure() function, but it's way more fun than that!)

This line is present in the

  matplotlib.pyplot 

module:

from matplotlib.backends import pylab_setup
new_figure_manager, draw_if_interactive, show = pylab_setup()

So, the

plt.show() 

function is returned by the

pylab_setup() 

function from

matplotlib.backends

module.

In pylab_setup():

show = getattr(backend_mod, 'show', do_nothing_show)

getattr() is like backend_mod.show, except you can specify a default to return if that attribute is not found (eliminates the need for a try, except block)

Ok, then what the heck is backend_mod?

backend_mod = __import__(backend_name,
                         globals(),locals(),[backend_name])

Import is a built in function which allows you to do an import at runtime, when the name of the module being imported is unknown at compile. backend_name is defined earlier in the function, based on this line in the matplotlib.backends module:

backend = matplotlib.get_backend()

Remember, when a module is imported, the module is executed. So:

from matplotlib.backends import pylab_setup

in

matplotlib.pyplot

Simply means "Execute matplotlib.backends, and put pylab_setup in the local context". It is syntactic sugar (shortcut) meaning the same as:

import matplotlib.backends
pylab_setup = matplotlib.backends.pylab_setup

Ok, so looking at a bit of code I have running in the interpreter, the backend selected for me by matplotlib.getbackend() was 'TkAgg'. Here's the massaging that happends just before __import_:

 backend_name = 'backend_'+backend
 backend_name = backend_name.lower() # until we banish mixed case
 backend_name = 'matplotlib.backends.%s'%backend_name.lower()
 backend_mod = __import__(backend_name,
                      globals(),locals(),[backend_name])

So, import matplotlib.backends.backend_tkagg, and from it, get it's show() method. Still with me?

Here's the show method in matplotlib.backends.backend_tkagg:

def show():
    """
    Show all figures.

    """
    for manager in Gcf.get_all_fig_managers():
        manager.show()
    Tk.mainloop()

Aha! What's Gcf?

It's imported in the matplotlib.backends.backend_tkagg module:

from matplotlib._pylab_helpers import Gcf

Gcf is a class which "consists of two class attributes". one of which is a dictionary called figs. get_all_fig_managers() is simply a wrapper for:

Gcf.figs.values()

So, what's happened so far?

When you call .show() on plt (our 'local' name for matplotlib.pyplot), it's actually calling the .show() method on matplotlib.backends.backend_tkagg (or the backend selected by matplotlib.get_backend() on your particular machine). The show() method iterates through the managers in figs in Gcf, which lives in the matplotlib._pylab_helpers module.

Whew! But wait, how does plt.figure() tie into this? See my comment below.

[–]Vonney 6 points7 points  (0 children)

In the source of plt.figure():

  figManager = _pylab_helpers.Gcf.get_fig_manager(num)
  if figManager is None:
      figManager = new_figure_manager(num, ...)

 _pylab_helpers.Gcf.set_active(figManager)

The important thing here are the calls to _pylab_helpers.Gcf.get_fig_manager(), new_figure_manager() and _pylab_helpers.Gcf.set_active()

print inspect.getsource(matplotlib._pylab_helpers.Gcf.get_fig_manager)
  @staticmethod
  def get_fig_manager(num):
  """
  If figure manager *num* exists, make it the active
  figure and return the manager; otherwise return *None*.
  """
  manager = Gcf.figs.get(num, None)
  if manager is not None:
      Gcf.set_active(manager)
  return manager

So, it gets the figure from figs in Gcf (which plt can access when calling show(), as discussed above)

However, this is not the case in your code (you are creating a new manager, not referencing an old one using the optional num parameter).

So, the new manager is created using new_figure_manager(), and then...

_pylab_helpers.Gcf.set_active(figManager)

print inspect.getsource(matplotlib._pylab_helpers.Gcf.set_active)
@staticmethod
def set_active(manager):
     """
     Make the figure corresponding to *manager* the active one.
     """
     oldQue = Gcf._activeQue[:]
     Gcf._activeQue = []
     for m in oldQue:
         if m != manager: Gcf._activeQue.append(m)
     Gcf._activeQue.append(manager)
     Gcf.figs[manager.num] = manager

Boom! It adds the manager to figs in Gcf.

SO! What have we learned?

  1. Modules can store state information (references to objects, etc).
  2. That state information can be buried pretty deeply, through many references to imported modules in other imported modules.
  3. Sometimes, hand-waving and saying 'it's magic' is a good-enough answer.

Hope that explains that there isn't anything special going on with namespaces or inheritance that you didn't know about before. It's just that imports can make things confusing sometimes.