3

You may already know, that in matplotlib 1.2.0 there is a new experimental feature, that figures are pickable (they can be saved with pickle module).

However, it doesn't work when one uses logscale, eg.

import matplotlib.pyplot as plt
import numpy as np
import pickle
ax = plt.subplot(111)
x = np.linspace(0, 10)
y = np.exp(x)
plt.plot(x, y)
ax.set_yscale('log')
pickle.dump(ax, file('myplot.pickle', 'w'))

results in:

PicklingError: Can't pickle <class 'matplotlib.scale.Log10Transform'>: attribute lookup matplotlib.scale.Log10Transform failed

Anybody knows any solution/workaround to this?

pms
  • 4,508
  • 4
  • 25
  • 30

1 Answers1

4

I've opened this as a bug report on matplotlib's github issue tracker. Its a fairly easy fix to implement on the matplotlib repository side (simply don't nest the Log10Transform class inside the LogScale class), but that doesn't really help you in being able to use this with mpl 1.2.0...

There is a solution to getting this to work for you in 1.2.0, but I warn you - its not pretty!

Based on my answer to a pickling question it is possible to pickle nested classes (as Log10Transform is). All we need to do is to tell Log10Transform how to "reduce" itself:

import matplotlib.scale

class _NestedClassGetter(object):
    """
    When called with the containing class as the first argument, 
    the name of the nested class as the second argument,
    and the state of the object as the third argument,
    returns an instance of the nested class.

    """
    def __call__(self, containing_class, class_name, state):
        nested_class = getattr(containing_class, class_name)
        # return an instance of a nested_class. Some more intelligence could be
        # applied for class construction if necessary.
        c = nested_class.__new__(nested_class)
        c.__setstate__(state)
        return c

def _reduce(self):
    # return a class which can return this class when called with the 
    # appropriate tuple of arguments
    cls_name = matplotlib.scale.LogScale.Log10Transform.__name__
    call_args = (matplotlib.scale.LogScale, cls_name, self.__getstate__())
    return (_NestedClassGetter(), call_args)

matplotlib.scale.LogScale.Log10Transform.__reduce__ = _reduce 

You might also decide to do this for other Log based transforms/classes, but for your example, you can now pickle (and successfully unpickle) your example figure:

import matplotlib.pyplot as plt
import numpy as np
import pickle


ax = plt.subplot(111)
x = np.linspace(0, 10)
y = np.exp(x)
plt.plot(x, y)
ax.set_yscale('log')

pickle.dump(ax, file('myplot.pickle', 'w'))
plt.savefig('pickle_log.pre.png')
plt.close()

pickle.load(file('myplot.pickle', 'r'))
plt.savefig('pickle_log.post.png')

I'm going to get on and fix this for mpl 1.3.x so that this nasty workaround isn't needed in the future :-) .

HTH,

Community
  • 1
  • 1
pelson
  • 21,252
  • 4
  • 92
  • 99
  • No problems. Question: Out of interest, how are you planning to use the pickling functionality? – pelson Nov 30 '12 at 13:35
  • 1
    So actually it's an indirect use. I mean: I'm creating a lot of plots, so I use `multiprocessing` to do it in parallel with many workers. Then, some of the plots created by the workers need to be returned to the main thread, so I use a `multiprocessing.Queue` in order to pass the figures back. What it does is basically pickling the objects that one inserts in the `Queue`. But I'm interested in direct use as well, just to save figures, as I'm used to with `xmgrace`, and edit them later on if necessary. I haven't done it yet though, so I don't know how would it work out in mpl. Best! – pms Nov 30 '12 at 19:12
  • 1
    multiprocessing and intermediate storage for something such as ipython (not yet done) were my primary motivators for doing this so its great to hear that! Thanks for letting me know! – pelson Dec 01 '12 at 08:06