1

I am dynamically creating classes using mixins, following the pattern in this SO answer, but it's giving me pickling errors:

https://stackoverflow.com/a/28205308/2056246

(In case anyone is curious why on earth I'd want to do this, it's really useful for machine learning when you might want to test different combinatorial pipelines of operations).

Here's a minimally reproducible example:

class AutoMixinMeta(ABCMeta):
    """
        Helps us conditionally include Mixins, which is useful if we want to switch between different
        combinations of models (ex. SBERT with Doc Embedding, RoBERTa with positional embeddings).

        class Sub(metaclass = AutoMixinMeta):
            def __init__(self, name):
            self.name = name
    """

    def __call__(cls, *args, **kwargs):
        try:
            mixin = kwargs.pop('mixin')
            if isinstance(mixin, list):
                mixin_names = list(map(lambda x: x.__name__, mixin))
                mixin_name = '.'.join(mixin_names)
                cls_list = tuple(mixin + [cls])
            else:
                mixin_name = mixin.__name__
                cls_list = tuple([mixin, cls])

            name = "{}With{}".format(cls.__name__, mixin_name)
            cls = type(name, cls_list, dict(cls.__dict__))
        except KeyError:
            pass
        return type.__call__(cls, *args, **kwargs)


class Mixer(metaclass = AutoMixinMeta):
    """ Class to mix different elements in.

            a = Mixer(config=config, mixin=[A, B, C])
    """
    pass


class A():
    pass

class B():
    pass

config={
    'test_a': True, 
    'test_b': True
}

def get_mixins(config):
    mixins = []
    if config['test_a']:
        mixins.append(A)
    if config['test_b']:
        mixins.append(B)
    return mixins

to_mix = get_mixins(config)

c = Mixer(mixin=to_mix)

import pickle
pickle.dump(c, open('tmp/test.pkl', 'wb'))
---------------------------------------------------------------------------
PicklingError                             Traceback (most recent call last)
<ipython-input-671-a7661c543ef8> in <module>
     23 
     24 import pickle
---> 25 pickle.dump(c, open('tmp/test.pkl', 'wb'))

PicklingError: Can't pickle <class '__main__.MixerWithA.B'>: attribute lookup MixerWithA.B on __main__ failed

I've read several answers focusing on using __reduce__:

https://stackoverflow.com/a/11526524/2056246 https://stackoverflow.com/a/11493777/2056246 Pickling dynamically created classes

But none of them deal with this dynamic creation on the metaclass level.

Has anyone run into this problem before/have any idea how to solve it? I don't know enough about metaclasses unfortunately to understand how to approach this problem.

Alex Spangher
  • 977
  • 2
  • 13
  • 22
  • Related question [here](https://stackoverflow.com/questions/4647566/pickle-a-dynamically-parameterized-sub-class/). Yes, this is possible via using a custom metaclass and `copyreg`. See also [issue 7689](https://bugs.python.org/issue7689). – Albert Apr 05 '23 at 20:46

1 Answers1

1

Not enough time right now for a full answer, with working code - but since you have the metaclass already, probably this can be made to work by simply implementing methods used by the pickling protocol on the metaclass itself - __getstate__ and __setstate__. Use the __qualname__ of the base classes as a tuple of strings as the serialized information you will need to re-create the pickled classes later.

(ping me in the comments if you are reading this 48h+ from now and there is no example code yet)

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • Thanks @jsbueno. What you're saying makes sense on a high level, but I'm a little unclear — I thought `__getstate__` and `__setstate__` were attributes of a class, not a metaclass. Ideally, I should learn a lot more about metaclasses but I'm under deadline pressure for a paper submission right now. – Alex Spangher Nov 05 '21 at 00:27
  • Anyway, I look forward to the code — I've already upvoted your answer and I will accept it soon – Alex Spangher Nov 05 '21 at 00:28
  • 1
    Sorry - I fiddled with this yesterday - it is a bit complicted due to the way it is used. There maybe a major shortcut, thugh: try using the extrnal package "dill" instead of the built-in pickle. It should handle the dynamic classes out of the box. – jsbueno Nov 07 '21 at 02:13
  • 1
    sorry for the delay - if I'm not mistaken, the `multiprocessing` package uses `pickle` under the hood so I have no control over this call – Alex Spangher Nov 15 '21 at 04:30