Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Cython >= 3.2.6 (including cython dev) issues related to KDTree picklability #34525

Description

@lesteve

Warning

This issue is not yet ready for a PR. If you are interested in contributing to scikit-learn, please have a look at our contributing guidelines, and in particular the sections for new contributors and the "Needs triage" label.

scipy-dev failure build log

A few related things:

From #34344 (comment)

One of the argument of the parallel call of kneighbors (at sklearn/neighbors/_base.py:916) raises AttributeError: 'dict' object has no attribute '__annotate__'. Did you mean: '__getstate__'? when unpickling it in the worker.

For the record, here is the traceback of the first failure:

___________________ test_knn_forcing_backend[ball_tree-loky] ___________________
[gw0] linux -- Python 3.14.6 /home/runner/miniconda3/envs/testvenv/bin/python
joblib.externals.loky.process_executor._RemoteTraceback: 
"""
Traceback (most recent call last):
  File "/home/runner/miniconda3/envs/testvenv/lib/python3.14/site-packages/joblib/externals/loky/process_executor.py", line 453, in _process_worker
    call_item = call_queue.get(block=True, timeout=timeout)
  File "/home/runner/miniconda3/envs/testvenv/lib/python3.14/multiprocessing/queues.py", line 120, in get
    return _ForkingPickler.loads(res)
           ~~~~~~~~~~~~~~~~~~~~~^^^^^
AttributeError: 'dict' object has no attribute '__annotate__'. Did you mean: '__getstate__'?
"""

The above exception was the direct cause of the following exception:

backend = 'loky', algorithm = 'ball_tree'

    @pytest.mark.thread_unsafe
    @pytest.mark.parametrize("backend", ["threading", "loky"])
    @pytest.mark.parametrize("algorithm", ALGORITHMS)
    def test_knn_forcing_backend(backend, algorithm):
        # Non-regression test which ensures the knn methods are properly working
        # even when forcing the global joblib backend.
        with joblib.parallel_backend(backend):
            X, y = datasets.make_classification(
                n_samples=30, n_features=5, n_redundant=0, random_state=0
            )
            X_train, X_test, y_train, y_test = train_test_split(X, y)
    
            clf = neighbors.KNeighborsClassifier(
                n_neighbors=3, algorithm=algorithm, n_jobs=2
            )
            clf.fit(X_train, y_train)
>           clf.predict(X_test)

X          = array([[-0.86122569,  1.91006495, -0.26800337,  2.55614791, -3.17228221],
       [ 0.61407937,  0.92220667,  0.3764255...865, -0.43782004,  1.17588368,  0.69480084],
       [ 1.84926373,  0.67229476,  0.40746184, -0.5832139 ,  1.00757175]])
X_test     = array([[ 0.1666735 ,  0.63503144,  2.38314477, -0.96222905,  0.90976274],
       [ 1.84926373,  0.67229476,  0.4074618...481,  1.92294203, -0.2894503 ,  1.3125179 ],
       [-1.31590741, -0.4615846 , -0.06824161,  0.36759048,  1.74228521]])
X_train    = array([[-0.87079715, -0.57884966, -0.31155253, -2.41378509, -1.86490941],
       [-0.76991607,  0.53924919, -0.6743326...829,  1.12663592,  0.31816612,  2.05477441],
       [-1.09306151, -1.49125759,  0.4393917 ,  2.06061532,  1.21697419]])
algorithm  = 'ball_tree'
backend    = 'loky'
clf        = KNeighborsClassifier(algorithm='ball_tree', n_jobs=2, n_neighbors=3)
y          = array([1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1,
       0, 0, 0, 1, 0, 0, 1, 0])
y_test     = array([0, 0, 0, 0, 1, 1, 1, 1])
y_train    = array([0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0])

../sklearn/neighbors/tests/test_neighbors.py:2120: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../sklearn/neighbors/_classification.py:278: in predict
    neigh_ind = self.kneighbors(X, return_distance=False)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        X          = array([[ 0.1666735 ,  0.63503144,  2.38314477, -0.96222905,  0.90976274],
       [ 1.84926373,  0.67229476,  0.4074618...481,  1.92294203, -0.2894503 ,  1.3125179 ],
       [-1.31590741, -0.4615846 , -0.06824161,  0.36759048,  1.74228521]])
        self       = KNeighborsClassifier(algorithm='ball_tree', n_jobs=2, n_neighbors=3)
../sklearn/neighbors/_base.py:916: in kneighbors
    chunked_results = Parallel(n_jobs, prefer="threads")(
        X          = array([[ 0.1666735 ,  0.63503144,  2.38314477, -0.96222905,  0.90976274],
       [ 1.84926373,  0.67229476,  0.4074618...481,  1.92294203, -0.2894503 ,  1.3125179 ],
       [-1.31590741, -0.4615846 , -0.06824161,  0.36759048,  1.74228521]])
        chunked_results = None
        ensure_all_finite = True
        n_jobs     = 2
        n_neighbors = 3
        n_samples_fit = 22
        query_is_train = False
        return_distance = False
        self       = KNeighborsClassifier(algorithm='ball_tree', n_jobs=2, n_neighbors=3)
        use_pairwise_distances_reductions = False
../sklearn/utils/parallel.py:91: in __call__
    return super().__call__(iterable_with_config_and_warning_filters)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        __class__  = <class 'sklearn.utils.parallel.Parallel'>
        config     = {'assume_finite': False, 'working_memory': 1024, 'print_changed_only': True, 'display': 'diagram', ...}
        filters_func = <function _get_filters at 0x7f9e42d93a00>
        iterable   = <generator object KNeighborsMixin.kneighbors.<locals>.<genexpr> at 0x7f9de05a17a0>
        iterable_with_config_and_warning_filters = <generator object Parallel.__call__.<locals>.<genexpr> at 0x7f9de05a3de0>
        self       = Parallel(n_jobs=2)
        warning_filters = [('ignore', re.compile('Class PassiveAggressive.+is deprecated', re.IGNORECASE), <class 'FutureWarning'>, None, 0), ('... shape on a NumPy array has been deprecated in NumPy 2.5', re.IGNORECASE), <class 'DeprecationWarning'>, None, 0), ...]
../../../../miniconda3/envs/testvenv/lib/python3.14/site-packages/joblib/parallel.py:2098: in __call__
    return output if self.return_generator else list(output)
                                                ^^^^^^^^^^^^
        _batched_calls_reducer_callback = <function Parallel.__call__.<locals>._batched_calls_reducer_callback at 0x7f9dd0da3530>
        backend_name = 'LokyBackend'
        iterable   = <generator object Parallel.__call__.<locals>.<genexpr> at 0x7f9de05a3de0>
        iterator   = <itertools.islice object at 0x7f9dd04ef470>
        n_jobs     = 2
        output     = <generator object Parallel._get_outputs at 0x7f9de0959340>
        pre_dispatch = 4
        self       = Parallel(n_jobs=2)
../../../../miniconda3/envs/testvenv/lib/python3.14/site-packages/joblib/parallel.py:1704: in _get_outputs
    yield from self._retrieve()
        _remaining_outputs = []
        detach_generator_exit = False
        dispatch_thread_id = 140317712021312
        iterator   = <itertools.islice object at 0x7f9dd04ef470>
        pre_dispatch = 4
        self       = Parallel(n_jobs=2)
../../../../miniconda3/envs/testvenv/lib/python3.14/site-packages/joblib/parallel.py:1806: in _retrieve
    self._raise_error_fast()
        nb_jobs    = 2
        self       = Parallel(n_jobs=2)
        timeout_control_job = None
../../../../miniconda3/envs/testvenv/lib/python3.14/site-packages/joblib/parallel.py:1885: in _raise_error_fast
    error_job.get_result(self.timeout)
        error_job  = <joblib.parallel.BatchCompletionCallBack object at 0x7f9dd035e600>
        self       = Parallel(n_jobs=2)
../../../../miniconda3/envs/testvenv/lib/python3.14/site-packages/joblib/parallel.py:780: in get_result
    return self._return_or_raise()
           ^^^^^^^^^^^^^^^^^^^^^^^
        backend    = <joblib._parallel_backends.LokyBackend object at 0x7f9dd02edcd0>
        self       = <joblib.parallel.BatchCompletionCallBack object at 0x7f9dd035e600>
        timeout    = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <joblib.parallel.BatchCompletionCallBack object at 0x7f9dd035e600>

    def _return_or_raise(self):
        try:
            if self.status == TASK_ERROR:
>               raise self._result
E               joblib.externals.loky.process_executor.BrokenProcessPool: A task has failed to un-serialize. Please ensure that the arguments of the function are all picklable.

self       = <joblib.parallel.BatchCompletionCallBack object at 0x7f9dd035e600>

../../../../miniconda3/envs/testvenv/lib/python3.14/site-packages/joblib/parallel.py:795: BrokenProcessPool

Here are the failing tests:

CI is still failing on [Unit tests Linux x86-64 pylatest_pip_scipy_dev](https://github.com/scikit-learn/scikit-learn/actions/runs/29715378564/job/88267642921) (Jul 20, 2026)

    test_kdtree_picklable_with_joblib[KDTree64]
    test_kdtree_picklable_with_joblib[KDTree32]
    test_knn_forcing_backend[ball_tree-loky]
    test_knn_forcing_backend[kd_tree-loky]
    test_knn_forcing_backend[auto-loky]

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions