multiprocessing: Can't pickle local object (tested)

Can't pickle local object (on Python 3.13: AttributeError: Can't get local object) means Pool.map was given something it can't send to a worker process. We measured five variants on Python 3.13.5 and the fixes that work.

Why it happens

Pool.map sends each task to a worker process by pickling it. Functions are pickled by name (module plus qualified name) and looked up again in the worker. Anything the worker can't find by name, or any object that can't be serialized, fails. This happens even with the fork start method, which is the default on Linux in 3.13.5 (we checked: get_start_method() returned fork).

1. Nested functions and lambdas

from multiprocessing import Pool

def main():
    def square(x):
        return x * x
    with Pool(2) as pool:
        try:
            print(pool.map(square, [1, 2, 3]))
        except Exception as e:
            print(type(e).__name__ + ":", e)
        try:
            print(pool.map(lambda x: x * x, [1, 2, 3]))
        except Exception as e:
            print(type(e).__name__ + ":", e)

if __name__ == "__main__":
    main()

Output:

AttributeError: Can't get local object 'main.<locals>.square'
AttributeError: Can't get local object 'main.<locals>.<lambda>'

Measured on Python 3.13.5: both raise AttributeError: Can't get local object 'main.<locals>.square'. Older Pythons word it Can't pickle local object (not re-tested here). A lambda at module level fails differently:

from multiprocessing import Pool

square = lambda x: x * x

if __name__ == "__main__":
    with Pool(2) as pool:
        try:
            print(pool.map(square, [1, 2, 3]))
        except Exception as e:
            print(type(e).__name__ + ":", e)

Output:

PicklingError: Can't pickle <function <lambda> at 0x7fe8a01cf560>: attribute lookup <lambda> on __main__ failed

2. Fix: a top-level function, plus functools.partial for extra arguments

from functools import partial
from multiprocessing import Pool

def power(x, exp):
    return x ** exp

if __name__ == "__main__":
    with Pool(2) as pool:
        print(pool.map(partial(power, exp=2), [1, 2, 3]))

Output:

[1, 4, 9]

Move the function to module level. If you were using a closure or lambda to bind an argument, partial of a top-level function pickles fine, as measured.

3. Bound methods carry the whole object

import threading
from multiprocessing import Pool

class Worker:
    def __init__(self):
        self.lock = threading.Lock()
    def run(self, x):
        return x * 2

if __name__ == "__main__":
    w = Worker()
    with Pool(2) as pool:
        try:
            print(pool.map(w.run, [1, 2, 3]))
        except Exception as e:
            print(type(e).__name__ + ":", e)

Output:

TypeError: cannot pickle '_thread.lock' object

Measured: passing w.run pickles w, including its threading.Lock, which raises TypeError: cannot pickle '_thread.lock' object. The same goes for open files, sockets, DB connections and clients. Drop them in __getstate__ and rebuild them in __setstate__:

import threading
from multiprocessing import Pool

class Worker:
    def __init__(self):
        self.lock = threading.Lock()
    def run(self, x):
        return x * 2
    def __getstate__(self):
        state = self.__dict__.copy()
        del state["lock"]                 # not picklable; rebuild in the worker
        return state
    def __setstate__(self, state):
        self.__dict__.update(state)
        self.lock = threading.Lock()

if __name__ == "__main__":
    with Pool(2) as pool:
        print(pool.map(Worker().run, [1, 2, 3]))

Output:

[2, 4, 6]

4. Results and exceptions go back through pickle too

from multiprocessing import Pool

def bad(x):
    class Result(Exception):
        pass
    raise Result(x)

if __name__ == "__main__":
    with Pool(2) as pool:
        try:
            pool.map(bad, [1])
        except Exception as e:
            print(type(e).__name__ + ":", e)

Output:

MaybeEncodingError: Error sending result: '<multiprocessing.pool.ExceptionWithTraceback object at 0x7f34d8ffea50>'. Reason: 'AttributeError("Can't get local object 'bad.<locals>.Result'")'

Measured: an exception class defined inside the worker function can't be sent back, so instead of your error you get MaybeEncodingError wrapping the pickling failure. Define exception classes, and any classes you return, at module level.

Frequently asked questions

How do I fix Can't pickle local object in multiprocessing?

Move the function out of the enclosing function to module level. Use functools.partial to bind extra arguments; we measured partial(power, exp=2) working with Pool.map.

Can I use a lambda with multiprocessing Pool.map?

No. We measured both a nested lambda (AttributeError: Can't get local object) and a module-level lambda (PicklingError: attribute lookup <lambda> on __main__ failed) failing on Python 3.13.5.

What does cannot pickle '_thread.lock' object mean?

Something you sent to a worker, often self via a bound method, holds a lock. Remove it in __getstate__ and recreate it in __setstate__, which we measured working.

What is MaybeEncodingError: Error sending result?

The worker's return value or exception couldn't be pickled back to the parent. We measured it with an exception class defined inside the worker function.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 on Linux (fork start method): nested functions, nested and module-level lambdas, functools.partial, a bound method holding a lock with and without __getstate__, and an unpicklable exception raised in a worker. Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.