pylab=inline fig.show() non-existent in notebook #1612

Closed
dwf opened this Issue Apr 16, 2012 · 11 comments

Comments

Projects
None yet
3 participants
Contributor

dwf commented Apr 16, 2012

I have some code that draws some plots and I notice it raises an AttributeError on the show() method of a figure object. I don't really know the details but I assume that IPython is somehow providing its own backend. Inspecting a figure object created with backend 'tk' or 'wx' I notice that show() doesn't seem to be a bound method but a function, pointing at the respective backend's implementation of show()?

It seems like figure objects should provide this in the context of notebook inline plotting, even if it's a no-op, for the sake of not breaking existing code.

Owner

fperez commented Apr 16, 2012

Could you provide us with a full traceback as well as at least the snippet that triggers the problem (if it's not easy to produce a small, self-contained example)? It will make the debugging far easier...

Contributor

dwf commented Apr 16, 2012

Hey Fernando,

It's just an AttributeError: 'Figure' object has no attribute 'show' with the traceback entirely in my own code.

Here's a snippet that runs fine in ipython --pylab but raises the AttributeError in ipython notebook --pylab=inline:

import numpy as np
import matplotlib.pyplot as plt

plt.ioff()                                                                      
x = np.random.uniform(-5, 5, size=(100))
y = np.random.uniform(-5, 5, size=(100))
f = plt.figure()
plt.scatter(x, y)
plt.plot(y)
f.show()
Owner

fperez commented Apr 16, 2012

Mmh, I see. It turns out that .show() is a method that gets added dynamically after the fact by the backends, which monkeypatch the figure object:

In [1]: f  =figure()

In [2]: f.show??
Type:       function
Base Class: <type 'function'>
String Form:<function <lambda> at 0xafe0cdc>
Namespace:  Interactive
File:       /home/fperez/usr/opt/lib/python2.7/site-packages/matplotlib/backends/backend_qt4.py
Definition: f.show(*args)
Source:             self.canvas.figure.show = lambda *args: self.window.show()

That's kind of nasty, because it means all backends have to manually go around patching up the figure objects they create. I'm pinging here @jdh2358 and @mdboom to get their take on this one... Guys, did I misunderstand things? I thought all a backend needed to do was to implement the handful of things our inline backend does... I'm happy to fix our code if that's really where the problem is (and probably will regardless, to alleviate @dwf's problems), but I'd like to also sort out the question at the MPL level.

Contributor

dwf commented Apr 16, 2012

Much as I've suspected. I've patched my own code to just catch the AttributeError and ignore it, so it doesn't matter much to me, but it might surprise other users who don't realize what's going on.

Granted, you're absolutely right re: MPL, it's a bit weird that backends are expected to do this monkey-patching, especially if it's not documented for new backend-writers. It seems like you could probably accomplish this with some backend-specific dispatch mechanism in the context of a more traditional show(), but there might be reasons for the current way of doing things.

jdh2358 commented Apr 16, 2012

This caught me by surprise too -- this looks like an undocumented feature. Eg, in backend_tkagg.py, we write (https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backends/backend_tkagg.py#L482):

    # attach a show method to the figure for pylab ease of use
    self.canvas.figure.show = lambda *args: self.show()

According to git blame, it's my fault.... I added it in 2007 - matplotlib/matplotlib@884a566. Git has a better memory than I do :-)

Easiest thing is for you to add it to your backend, and we will document it in backend_bases.py and backend_template.py -- I created this issue matplotlib/matplotlib#835

Owner

fperez commented Apr 16, 2012

John, wouldn't it be cleaner to have a show method in the base Figure object, that simply does

def show(self):
   plt.show(self)

? That way it would cover every backend out of the box, without the need to go manually fixing each one (for example, we don't know if the native OSX one is doing this correctly or not, I can't test that one).

jdh2358 commented Apr 16, 2012

Figure instance doesn't know about pyplot. And I'm reasonably sure macosx doesn't implement this because it did not exist when I made the initial patch, and since it wasn't properly documented Michiel wouldn't have known to add it most likely

Owner

fperez commented Apr 16, 2012

Understood, but my point stands (perhaps with a different spelling): isn't it possible to fix this at the base figure/canvas level and have it simply delegate to what the backends already do? It seems that the necessary fix is effectively to copy boilerplate equivalent to:

self.canvas.figure.show = lambda *args: self.show()

into every backend. If that's really the case, then the right solution is to do it once in the right place, instead of having N copies of this more or less identical code in every single backend (with bugs in those who fail/forget to do so).

jdh2358 commented Apr 16, 2012

It could be possible, but these are expressed in the current implementation at the level of the FigureManager. The Figure knows about the FigureCanvas, but not about the FigureManager. FigureManager is an abstraction designed for pyplot, which manages raising the figure etc. People who are embedding mpl in the GUI are probably not using the manager at all. Basically, it is a convenience method hack for pyplot users who want to use a figure method. We could do something like:

class Figure:
    def show(self):
        manager = getattr(self.canvas, 'manager')
        if manager is not None:
            manager.show()

# and then define a do-nothing default "def show" for the manager (so backend implementers are aware to target it)

class FigureManagerBase:
    def show(self):
        'raise the figure window and redraw'
        pass

but we don't have anything like this concept currently (it's a series of monkey patches) and testing and implementing across backends and toolkits is a bit of work. Stay tuned.

Owner

fperez commented Apr 16, 2012

Got it; we'll fix it on our side in the backend, and mpl can consider a longer-term design for this.

@fperez fperez added a commit to fperez/ipython that referenced this issue Apr 17, 2012

@fperez fperez Add show() method to figure objects.
See #1612 and matplotlib/matplotlib#835 for
more details.
801b99c
Owner

fperez commented Apr 17, 2012

@dwf, could you test #1615 and let us know if it fixes your problem (without introducing any new ones)?

fperez closed this in 8df0574 Apr 18, 2012

@mattvonrocketstein mattvonrocketstein pushed a commit to mattvonrocketstein/ipython that referenced this issue Nov 3, 2014

@fperez fperez Add show() method to figure objects.
See #1612 and matplotlib/matplotlib#835 for
more details.
c007971

@mattvonrocketstein mattvonrocketstein pushed a commit to mattvonrocketstein/ipython that referenced this issue Nov 3, 2014

@fperez fperez Merge pull request #1615 from fperez/inline_figshow
Add show() method to figure objects.

This is a hack, but needed to match the monkeypatching applied by matplotlib in some backends (not all).

See #1612 and matplotlib/matplotlib#835 for more details.

Closes #1612.
90c2d26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment