statistics: kde() Returns a Closure That Can't Be Pickled (tested)
statistics.kde() is new in Python 3.13 -- it fits a kernel density estimate to a dataset and hands back something you call like a function to get the estimated density at any point. We measured what that returned object actually is, and whether it survives being pickled.
1. kde() returns a plain function, not a reusable, inspectable object
import statistics as st
data = [1, 2, 3, 4, 5]
density = st.kde(data, h=1)
print(type(density))
print(density(3))
print(repr(density))
<class 'function'>
0.1981731324932191
<function kde.<locals>.pdf at 0x7f9712d15620>
Measured: kde() returns an ordinary Python function -- specifically a closure defined inside kde()'s own body (its repr literally says kde.<locals>.pdf). Calling it works exactly like the documentation shows. There's no separate model object with its own methods or attributes -- just this one callable.
2. That closure can't be pickled
import pickle
import statistics as st
data = [1, 2, 3, 4, 5]
density = st.kde(data, h=1)
try:
pickle.dumps(density)
except AttributeError as e:
print("pickle failed:", e)
pickle failed: Can't get local object 'kde.<locals>.pdf'
Measured: pickle.dumps() raises AttributeError because the function returned by kde() is defined inside another function (a closure), and Python's pickle module can only pickle functions it can look up by their module-level qualified name -- a locally-defined function has no such importable path. This breaks silently-expected workflows: caching a fitted density estimate to disk with pickle, sending it to a multiprocessing.Pool worker, or storing it in a joblib/Redis cache all rely on pickling, and all fail the same way.
3. The underlying data and parameters aren't recoverable from the returned function either
import statistics as st
data = [1, 2, 3, 4, 5]
density = st.kde(data, h=1)
print(density.__closure__ is not None)
print(hasattr(density, "data"))
print(hasattr(density, "h"))
True
False
False
Measured: the fitted data and bandwidth are captured in the function's __closure__ cells, but there's no public attribute exposing them -- density.data and density.h both don't exist. If a fitted kde() result needs to survive a process boundary or be written to disk, the only option is re-running kde() on the original data and h again on the other side -- which means those two values need to be saved separately, not the function kde() returns.
Frequently asked questions
Can you pickle the function returned by statistics.kde()?
No -- we measured pickle.dumps() on it raising AttributeError: Can't get local object 'kde.<locals>.pdf', because kde() returns a closure defined inside its own function body, which pickle can't look up by name.
Why can't statistics.kde()'s return value be sent to a multiprocessing worker?
Because it can't be pickled -- multiprocessing.Pool relies on pickle to send callables to worker processes, and we measured the kde() closure failing to pickle with the same AttributeError as a direct pickle.dumps() call.
Does statistics.kde()'s returned function expose the original data or bandwidth?
No -- we measured it having no public data or h attribute, even though both values are captured internally in the function's closure cells. Recovering them means keeping the original arguments and calling kde() again.
How do I cache or reuse a statistics.kde() result across processes?
Save the original data list and the h/kernel arguments instead of the returned function, and call kde() again wherever you need it -- we measured the function itself as fundamentally unpicklable, so there's no way to serialize the fitted result directly.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in statistics module: called the new (3.13) kde() function and tried to pickle the object it returns. PlainHeaders is our extension; Python's statistics module is built in.