Search code examples
pythonmatplotlibweak-references

Matplotlib "dictionary changed size during iteration" error when creating subplot


I wrote a function that plots a figure consisting of two subplots of different sizes:

def draw_plot(data, function, sigma_value):

    gs = gridspec.GridSpec(1, 5)
    ax1 = subplot(gs[0, 0:3])  
    ax2 = subplot(gs[0, 3:5], sharey=ax1)                
    gs.update(wspace=0.05)
    ...

I should mention that this is a module-level function, so at the top of that module I make imports

from pylab import *
import matplotlib.gridspec as gridspec

When I run myplot.draw_plot(...), I get RuntimeError. The thing is this behaviour is inconsistent. I can call the function, say, three times, and the first two times I get the error, whereas the third time it runs OK.

The Traceback is

Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
File "myplot.py", line 105, in draw_plot
    ax1 = subplot(gs[0, 0:3])                 
File "C:\Python32\lib\site-packages\matplotlib\pyplot.py", line 766, in subplot
    a = fig.add_subplot(*args, **kwargs)
File "C:\Python32\lib\site-packages\matplotlib\figure.py", line 779, in add_subplot
    a = subplot_class_factory(projection_class)(self, *args, **kwargs)
File "C:\Python32\lib\site-packages\matplotlib\axes.py", line 8380, in __init__
    self._axes_class.__init__(self, fig, self.figbox, **kwargs)
File "C:\Python32\lib\site-packages\matplotlib\axes.py", line 467, in __init__
    self.cla()
File "C:\Python32\lib\site-packages\matplotlib\axes.py", line 910, in cla
    self._shared_y_axes.clean()
File "C:\Python32\lib\site-packages\matplotlib\cbook.py", line 1493, in clean
    for key, val in mapping.items():
RuntimeError: dictionary changed size during iteration

Thanks for any help!

EDIT

Obviously I've been trying to figure out myself what is going on, so following the Traceback I checked the clean() function in cbook.py.

def clean(self):
    """
    Clean dead weak references from the dictionary
    """
    mapping = self._mapping
    for key, val in mapping.items():
        if key() is None:
            del mapping[key]
            val.remove(key)

In the function I added a line that would print mapping.items() and I noticed that the error occurs when there are entries similar to <weakref at 0480EBA0; dead> among those items. I'm totally unfamiliar with weak references so I'm stuck again.

EDIT 2 It's certainly not a good solution but commenting out the clean() function body helps in my case without producing any new errors.


Solution

  • I've just found a very recent post Safely iterating over WeakKeyDictionary and WeakValueDictionary that helps with a very similar issue.

    So using answer given by Bakuriu I edited close() function as following

    def clean(self):
        """
        Clean dead weak references from the dictionary
        """
    
        mapping = self._mapping
        for key, val in list(mapping.items()):  # iterate over list now        
            if key() is None:
                del mapping[key]
                val.remove(key)
    

    and it seems to be working just fine!

    EDIT

    I've just found out that in a new version of matplotlib the function looks like this:

    def clean(self):
        """
        Clean dead weak references from the dictionary
        """
        mapping = self._mapping
        to_drop = [key for key in mapping if key() is None]
        for key in to_drop:
            val = mapping.pop(key)
            val.remove(key)
    

    Source:

    https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/cbook.py