Repository: joblib/joblib
Pull request #1549 (2024-02-20 23:47:46)

n_jobs parameter in instantiation of Parallel should be an integer
Based on https://github.com/joblib/joblib/issues/1539

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/joblib/joblib/pull/1549?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=joblib) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`6310841`)](https://app.codecov.io/gh/joblib/joblib/commit/6310841f66352bbf958cc190a973adcca611f4c7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=joblib) 95.14% compared to head [(`44f89ca`)](https://app.codecov.io/gh/joblib/joblib/pull/1549?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=joblib) 95.09%.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1549      +/-   ##
==========================================
- Coverage   95.14%   95.09%   -0.05%     
==========================================
  Files          44       44              
  Lines        7562     7567       +5     
==========================================
+ Hits         7195     7196       +1     
- Misses        367      371       +4     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/joblib/joblib/pull/1549?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=joblib).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=joblib).

fcharras: Thanks for the PR @androids-electric-sheep, can you give a bit more context about what is the current behavior, does it fail if it's not an int ? (what about n_jobs=3.0, n_jobs="3"). So we know if the PR is about generating a better, earlier error message, or actually add constraints on the input type that didn't exist before.
androids-electric-sheep: From the issue this PR is based on, and reproduced locally using the example code provided, if a float is provided to the n_jobs parameter slicing will later fail because it requires an int. 

So it's a combination of both really, adding the type constraint and then providing a more understandable error message if it's violated
tomMoral: Hello! Thanks for the PR!
We decided to go with a more permissive behavior (try to convert to int else fail).

We are trying to make a release by Friday so I changed the PR directly and made the test cover more cases.
Thanks for the contrib!

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 3b802e0..5bf9c95 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,11 @@ Latest changes
 In development
 --------------
 
+- Try to cast ``n_jobs`` to int in parallel and raise an error if
+  it fails. This means that ``n_jobs=2.3`` will now result in
+  ``effective_n_jobs=2`` instead of failing.
+  https://github.com/joblib/joblib/pull/1539
+
 - Ensure that errors in the task generator given to Parallel's call
   are raised in the results consumming thread.
   https://github.com/joblib/joblib/pull/1491
diff --git a/joblib/parallel.py b/joblib/parallel.py
index af842b4..dea1976 100644
--- a/joblib/parallel.py
+++ b/joblib/parallel.py
@@ -1287,6 +1287,10 @@ class Parallel(Logger):
             # No specific context override and no specific value request:
             # default to the default of the backend.
             n_jobs = backend.default_n_jobs
+        try:
+            n_jobs = int(n_jobs)
+        except ValueError:
+            raise ValueError("n_jobs could not be converted to int")
         self.n_jobs = n_jobs
 
         if (require == 'sharedmem' and


Test patch:
diff --git a/joblib/test/test_parallel.py b/joblib/test/test_parallel.py
index 2c32307..21f06fa 100644
--- a/joblib/test/test_parallel.py
+++ b/joblib/test/test_parallel.py
@@ -633,6 +633,29 @@ def test_invalid_njobs(backend):
         Parallel(n_jobs=0, backend=backend)._initialize_backend()
     assert "n_jobs == 0 in Parallel has no meaning" in str(excinfo.value)
 
+    with raises(ValueError) as excinfo:
+        Parallel(n_jobs=0.5, backend=backend)._initialize_backend()
+    assert "n_jobs == 0 in Parallel has no meaning" in str(excinfo.value)
+
+    with raises(ValueError) as excinfo:
+        Parallel(n_jobs="2.3", backend=backend)._initialize_backend()
+    assert "n_jobs could not be converted to int" in str(excinfo.value)
+
+    with raises(ValueError) as excinfo:
+        Parallel(n_jobs="invalid_str", backend=backend)._initialize_backend()
+    assert "n_jobs could not be converted to int" in str(excinfo.value)
+
+
+@with_multiprocessing
+@parametrize('backend', PARALLEL_BACKENDS)
+@parametrize('n_jobs', ['2', 2.3, 2])
+def test_njobs_converted_to_int(backend, n_jobs):
+    p = Parallel(n_jobs=n_jobs, backend=backend)
+    assert p._effective_n_jobs() == 2
+
+    res = p(delayed(square)(i) for i in range(10))
+    assert all(r == square(i) for i, r in enumerate(res))
+
 
 def test_register_parallel_backend():
     try:
@@ -1910,7 +1933,7 @@ def test_threadpool_limitation_in_child_loky(n_jobs):
     # Skip this test if numpy is not linked to a BLAS library
     parent_info = _check_numpy_threadpool_limits()
     if len(parent_info) == 0:
-        pytest.skip(msg="Need a version of numpy linked to BLAS")
+        pytest.skip(reason="Need a version of numpy linked to BLAS")
 
     workers_threadpool_infos = Parallel(backend="loky", n_jobs=n_jobs)(
         delayed(_check_numpy_threadpool_limits)() for i in range(2))
@@ -1936,7 +1959,7 @@ def test_threadpool_limitation_in_child_context(
     # Skip this test if numpy is not linked to a BLAS library
     parent_info = _check_numpy_threadpool_limits()
     if len(parent_info) == 0:
-        pytest.skip(msg="Need a version of numpy linked to BLAS")
+        pytest.skip(reason="Need a version of numpy linked to BLAS")
 
     with context('loky', inner_max_num_threads=inner_max_num_threads):
         workers_threadpool_infos = Parallel(n_jobs=n_jobs)(