I would like to use a decorator on a function that I will subsequently pass to a multiprocessing pool. However, the code fails with “PicklingError: Can’t pickle : attribute lookup __builtin__.function failed”. I don’t quite see why it fails here. I feel certain that it’s something simple, but I can’t find it. Below is a minimal “working” example. I thought that using the functools function would be enough to let this work.
If I comment out the function decoration, it works without an issue. What is it about multiprocessing that I’m misunderstanding here? Is there any way to make this work?
Edit: After adding both a callable class decorator and a function decorator, it turns out that the function decorator works as expected. The callable class decorator continues to fail. What is it about the callable class version that keeps it from being pickled?
import random
import multiprocessing
import functools
class my_decorator_class(object):
def __init__(self, target):
self.target = target
try:
functools.update_wrapper(self, target)
except:
pass
def __call__(self, elements):
f = []
for element in elements:
f.append(self.target([element])[0])
return f
def my_decorator_function(target):
@functools.wraps(target)
def inner(elements):
f = []
for element in elements:
f.append(target([element])[0])
return f
return inner
@my_decorator_function
def my_func(elements):
f = []
for element in elements:
f.append(sum(element))
return f
if __name__ == '__main__':
elements = [[random.randint(0, 9) for _ in range(5)] for _ in range(10)]
pool = multiprocessing.Pool(processes=4)
results = [pool.apply_async(my_func, ([e],)) for e in elements]
pool.close()
f = [r.get()[0] for r in results]
print(f)
The problem is that pickle needs to have some way to reassemble everything that you pickle. See here for a list of what can be pickled:
http://docs.python.org/library/pickle.html#what-can-be-pickled-and-unpickled
When pickling
my_func, the following components need to be pickled:An instance of
my_decorator_class, calledmy_func.This is fine. Pickle will store the name of the class and pickle its
__dict__contents. When unpickling, it uses the name to find the class, then creates an instance and fills in the__dict__contents. However, the__dict__contents present a problem…The instance of the original
my_functhat’s stored inmy_func.target.This isn’t so good. It’s a function at the top-level, and normally these can be pickled. Pickle will store the name of the function. The problem, however, is that the name "my_func" is no longer bound to the undecorated function, it’s bound to the decorated function. This means that pickle won’t be able to look up the undecorated function to recreate the object. Sadly, pickle doesn’t have any way to know that object it’s trying to pickle can always be found under the name
__main__.my_func.You can change it like this and it will work:
You have observed that the decorator function works when the class does not. I believe this is because
functools.wrapsmodifies the decorated function so that it has the name and other properties of the function it wraps. As far as the pickle module can tell, it is indistinguishable from a normal top-level function, so it pickles it by storing its name. Upon unpickling, the name is bound to the decorated function so everything works out.