Search code examples
pythonpython-3.xpicklepython-multiprocessingmetaclass

Pickle a function for multiprocessing by making it reload another


Suppose I have the following simple class:

class HelloWorld:
   def call(self):
       print('Hello World!')

Then I can use the HelloWorld.call with multiprocessing though python knows how to pickle HelloWorld.call. However, say I want to wrap that function in a metaclass,

class MetaClass(type):
    def __new__(mcs, name, bases, dct):

        def wrap(f):
            def wrapper(self):
                print(f.__qualname__)
                f(self)

            return wrapper

        new_dct = dict()
        for attr_name in dct.keys():
            if callable(dct[attr_name]):
                new_dct[attr_name] = wrap(dct[attr_name])
            else:
                new_dct[attr_name] = dct[attr_name]

        return type.__new__(mcs, name, bases, new_dct)

class HelloWorld(metaclass=MetaClass):
   def call(self):
       print('Hello World!')

Then I can not use HelloWorld.call with multiprocessing, as it will not pickle. What I want is to cause python not use the wrapper-function for pickling, but rather the original function (though after unpickling it will refer to the wrapped function by default).

Any suggestions? Thanks!


Solution

  • Looking at the source code, you can see that ForkingPickler (multiprocessing's custom Pickler) pickles by the method __func__-attribute's __name__. So what I had to do is to set wrapper.__name__ to be the same as the original member's name:

    class MetaClass(type):
        def __new__(mcs, name, bases, dct):
    
            def wrap(f):
                def wrapper(self):
                    print(f.__qualname__)
                    f(self)
    
                #############################
                wrapper.__name__ = f.__name__
                #############################
    
                return wrapper
    
            new_dct = dict()
            for attr_name in dct.keys():
                if callable(dct[attr_name]):
                    new_dct[attr_name] = wrap(dct[attr_name])
                else:
                    new_dct[attr_name] = dct[attr_name]
    
            return type.__new__(mcs, name, bases, new_dct)
    
    class HelloWorld(metaclass=MetaClass):
       def call(self):
           print('Hello World!')
    

    One could also use functools.update_wrapper(wrapper, f) instead of just setting __name__. Will also work.