0

In Python 3.5, I made a very simple subclass of dict, that uses synonymous keys (stored as a dictionary attribute):

class synonymDict(dict):

    def __init__(self, synonyms=None, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        self.synonyms = {} if synonyms is None else synonyms

    def __getitem__(self, name):
        return dict.__getitem__(self, self.synonyms.get(name, name))

    def __setitem__(self, name, value):
        dict.__setitem__(self, self.synonyms.get(name, name), value)

    def __repr__(self):
        return 'synonymDict(%s)' % dict.__repr__(self)

Now, imagine I define this class in myPackage.myTools; I tried pickling and unpickling (for use with multiprocessing), but I get the following Attribute error:

>>> from myPackage import myTools
>>> synonyms = dict(A='a', B='b')
>>> mymapped = myTools.synonymDict(synonyms)
>>> mymapped['A'] = 36
>>> mymapped
synonymDict({'a': 36})
>>> mymapped.synonyms
{'A': 'a', 'B': 'b'}
>>> import pickle
>>> pickle.loads(pickle.dumps(mymapped))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/users/ldog/glouvel/install/python3/LibsDyogen/myPhylTree.py", line 35, in __setitem__
    dict.__setitem__(self, self.synonyms.get(name, name), value)
AttributeError: 'synonymDict' object has no attribute 'synonyms'

Why this error during unpickling? Should I make a deepcopy of synonyms? Is it a subclassing problem or a namespace problem?

PlasmaBinturong
  • 2,122
  • 20
  • 24

1 Answers1

0

According to this answer https://stackoverflow.com/a/46560454/4614641, I can fix my problem with a single line: defining a default synonyms class attribute.

New code:

class synonymDict(dict):
    synonyms = {}  # Needed for unpickling!

    def __init__(self, synonyms=None, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        self.synonyms = {} if synonyms is None else synonyms

    def __getitem__(self, name):
        return dict.__getitem__(self, self.synonyms.get(name, name))

    def __setitem__(self, name, value):
        dict.__setitem__(self, self.synonyms.get(name, name), value)

    def __repr__(self):
        return 'synonymDict(%s)' % dict.__repr__(self)

That's because the unpickling (see https://docs.python.org/3.5/library/pickle.html#pickling-class-instances) does not call the __init__ method of the class, but it tries to call __setitem__ first! Which I had overridden to call an attribute that is only defined in __init__...

PlasmaBinturong
  • 2,122
  • 20
  • 24