Quick Answer
Python’s pickle serializes a function by its qualified module name, not by copying its code. Define multiprocessing workers at module level, protect process startup with if __name__ == '__main__':, avoid lambdas and nested functions, and use functools.partial only with a top-level callable and picklable arguments.

The error AttributeError: Can't pickle local object means Python tried to serialize a function, class, or callable that was defined inside another function or expression. The pickle module cannot import that object by a stable module-level name.
The main references are Python’s pickle documentation, the multiprocessing start-method documentation, and functools.partial.
The best fix is not to force the object into a broader namespace at runtime. Define functions and classes at the top level of an importable module, then pass plain data into them.
This matters even more with multiprocessing. On Python 3.14, fork is no longer the default start method on any platform, so code that only worked by inheriting local state is more likely to fail.
The error is common in notebooks because code cells are easy to rearrange and worker functions are often created inside helper functions. For production code, put multiprocessing workers in a normal module file and import them from the script that starts the pool.
Some libraries use serializers such as cloudpickle to handle more Python objects, but that should not be the first fix for normal application code. A top-level function is simpler, more portable, and easier for another process to import.
Why Local Objects Fail
A nested function is local to the function that created it. Pickle cannot reload it later by importing a module path.
import pickle
def build_worker():
def worker(number):
return number * 2
return worker
task = build_worker()
pickle.dumps(task)
This code is valid Python, but it is not pickle-friendly. The object name includes a local scope, and another process cannot import it directly.
The same problem can happen with nested classes, lambdas, closures, and callables created dynamically inside a function.
If the object name contains <locals> in an error message, that is the clue. It means the callable lives inside another scope instead of at module level.

Move The Function To Module Level
Define the function at the top level of a module so pickle can reference it by module and name.
import pickle
def worker(number):
return number * 2
payload = pickle.dumps(worker)
restored_worker = pickle.loads(payload)
print(restored_worker(5))
This works because worker can be found as an importable top-level function.
In a real project, put worker functions in a normal Python file that the main program can import. Avoid defining them inside request handlers, notebook cells that move around, or setup functions.
For package code, keep workers near the data-processing logic, not buried inside the command-line entry point. The entry point should wire inputs together; the worker should remain importable.
Use Top-Level Classes Too
Classes must follow the same rule: define them where a new Python process can import them.
import pickle
from dataclasses import dataclass
@dataclass
class Job:
job_id: int
label: str
job = Job(7, "build")
data = pickle.dumps(job)
print(pickle.loads(data))
Pickle stores the class reference and instance data. It does not store the class source code itself, so the class definition must still be available when loading.
If you rename or move the class after writing old pickle data, loading that old data can fail. That is one reason pickle is a poor long-term storage format for public data.

Use Multiprocessing Safely
For multiprocessing, keep the worker top-level and protect process startup with if __name__ == "__main__".
from multiprocessing import get_context
def square(number):
return number * number
if __name__ == "__main__":
context = get_context("spawn")
with context.Pool() as pool:
print(pool.map(square, [1, 2, 3, 4]))
The spawn start method launches a fresh interpreter, imports the main module, and then runs the worker. That means the worker must be importable.
This pattern also works well on Windows and macOS, where spawn-style behavior is common. It avoids relying on process memory inherited from a parent process.
The main guard prevents child processes from starting new pools while importing the main module. Without it, multiprocessing code can recurse or fail during startup.

Avoid Lambdas And Closures
Lambdas and closures are convenient, but they are often the wrong shape for pickle.
import pickle
def make_multiplier(factor):
return lambda number: number * factor
task = make_multiplier(3)
try:
pickle.dumps(task)
except AttributeError as error:
print(error)
Use a named top-level function instead. If you need to bind an extra argument, use a small data object or functools.partial with a top-level function.
Do not hide business logic inside a closure when that logic must cross a process boundary.
When a closure exists only to remember a value, pass that value as data instead. That makes the function easier to test and keeps the serialized object small.
Bind Arguments With partial
functools.partial can be pickle-friendly when the wrapped function is top-level and the bound arguments are picklable.
import pickle
from functools import partial
def multiply(number, factor):
return number * factor
triple = partial(multiply, factor=3)
data = pickle.dumps(triple)
print(pickle.loads(data)(5))
This keeps the executable code importable and stores only the bound data needed for the call.
The practical rule is simple: pickle stores references to importable functions and classes, not local source code. Move callable objects to module level, pass plain data, use the main guard for multiprocessing, and never unpickle data from untrusted sources.
If the design still needs to send behavior between processes, reconsider the process boundary. Sending task names and plain arguments is usually more reliable than sending dynamically created functions.

Move the Worker to Module Scope
A nested function belongs to the local scope of another call, so a child process cannot reliably import it by module-qualified name. Define the worker at the top level of a module that the child can import.
from multiprocessing import Pool
def square(value):
return value * value
def main():
with Pool() as pool:
print(pool.map(square, [1, 2, 3]))
if __name__ == "__main__":
main()
The main guard is especially important on platforms that start child processes by importing the main module. Without it, process creation can recurse or re-run setup code.
Use partial Instead of a Closure
functools.partial can bind simple arguments to a top-level function while keeping the callable importable.
from functools import partial
from multiprocessing import Pool
def multiply(value, factor):
return value * factor
double = partial(multiply, factor=2)
with Pool() as pool:
print(pool.map(double, [1, 2, 3]))
Every bound argument still needs to be picklable. If you use a custom class, define it at module scope and test the same start method used in production. Never unpickle data from an untrusted source.
For related serialization workflows, compare copying Python files with conditional imports when a local function crosses a module boundary.
Frequently Asked Questions
Why does Python say it cannot pickle a local object?
The object is defined inside a function or another local scope, so pickle cannot resolve its qualified name from an importable module.
How do I make a multiprocessing function picklable?
Move it to module scope, pass picklable arguments, and protect process startup with an if __name__ == ‘__main__’ guard.
Can lambda functions be pickled in Python?
Standard pickle cannot reliably pickle lambda functions because they do not have a stable importable name. Use a top-level def instead.
Can functools.partial fix a local object pickle error?
It can bind arguments to a top-level function, but the function and every bound argument still need to be picklable.
globals work to save but not to load the pickled file, any recommendations on that?
Is your variable saved in local context? Can you add your code here so that I can understand whats going on?
That was the issue, I needed to define the variable again when loading, thanks a lot for the reply!
Attribute error while multiprocessing – can’t pickle local objects:
Works perfectly. Really helpful thanks a lot.