Search code examples
pythonpickledill

Dynamic inheritance that is pickleable?


After much searching, the only way I have found to solve my particular problem is to use dynamic inheritance. It is easy enough following the guide from here and a few other SO questions; most table is this.

Using a modified version of the contrived example from the first link:

def makeinst(cls, *args, **kwargs):
    class NewClass(cls): pass
    return NewClass(*args, **kwargs)
mylist = makeinst(list,(1,2))

This works as I would hope but it can't be pickled:

pickle.dumps(mylist)
...

AttributeError: Can't pickle local object 'makeinst.<locals>.NewClass'

I understand why this doesn't work but what I want to know is there a way around it? Is there a better way to dynamically subclass something?

(FWIW, dill can't do it either. See dill issue #56)


Solution

  • You can create the class in the global scope of the module, to do that you need to create a class manually with the type(name, bases, class_dict) call

    import pickle
    
    def makeinst(name, cls, *args, **kwargs):
    
        # This will be a method
        def foo(self):
            return f"I am: {self!r}"
    
        globals()[name] = type(
            name,
            # This must be a tuple
            # (cls) evaluate to cls
            # (cls,) evaluates to a tuple containing cls as its only element
            (cls,),
            # Methods, classmethods, staticmethods and all class-level data
            {
                "foo": foo
            },
        )
    
        return globals()[name](*args, **kwargs)
    
    my_list = makeinst("MyList", list, [1, 2, 3])
    print(my_list) # [1, 2, 3]
    
    data = pickle.dumps(my_list)
    my_list_unpickled = pickle.loads(data)
    print(my_list_unpickled) # [1, 2, 3]
    
    print(my_list_unpickled.foo()) # I am: [1, 2, 3]
    

    In another program execution you must call makeinst("MyList", list) at least once before unpickling to define the class.