Skip to content

Commit 4a1ca7d

Browse files
DEP: lib: Deprecate acceptance of float (and more) in bincount. (numpy#27076)
The first argument of bincount is expected to contain integers. As noted in numpygh-3138, it actually accepts floats, and casts them to integers with no warnings. (It also accepts other objects that can be cast to integers, so inputs such as ["1", Fraction(5, 3)] are accepted, with fractional parts silently dropped.) This change deprecates that behavior. Now a deprecation warning is generated if the input cannot be safely cast to integer. Closes numpygh-3138. Co-authored-by: Sebastian Berg <[email protected]>
1 parent bb391b5 commit 4a1ca7d

File tree

3 files changed

+61
-3
lines changed

3 files changed

+61
-3
lines changed

numpy/_core/src/multiarray/compiled_base.c

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ minmax(const npy_intp *data, npy_intp data_len, npy_intp *mn, npy_intp *mx)
101101
* arr_bincount is registered as bincount.
102102
*
103103
* bincount accepts one, two or three arguments. The first is an array of
104-
* non-negative integers The second, if present, is an array of weights,
104+
* non-negative integers. The second, if present, is an array of weights,
105105
* which must be promotable to double. Call these arguments list and
106106
* weight. Both must be one-dimensional with len(weight) == len(list). If
107107
* weight is not present then bincount(list)[i] is the number of occurrences
@@ -130,9 +130,57 @@ arr_bincount(PyObject *NPY_UNUSED(self), PyObject *const *args,
130130
return NULL;
131131
}
132132

133-
lst = (PyArrayObject *)PyArray_ContiguousFromAny(list, NPY_INTP, 1, 1);
133+
/*
134+
* Accepting arbitrary lists that are cast to NPY_INTP, possibly
135+
* losing precision because of unsafe casts, is deprecated. We
136+
* continue to use PyArray_ContiguousFromAny(list, NPY_INTP, 1, 1)
137+
* to convert the input during the deprecation period, but we also
138+
* check to see if a deprecation warning should be generated.
139+
* Some refactoring will be needed when the deprecation expires.
140+
*/
141+
142+
/* Check to see if we should generate a deprecation warning. */
143+
if (!PyArray_Check(list)) {
144+
/* list is not a numpy array, so convert it. */
145+
PyArrayObject *tmp1 = (PyArrayObject *)PyArray_FromAny(
146+
list, NULL, 1, 1,
147+
NPY_ARRAY_DEFAULT, NULL);
148+
if (tmp1 == NULL) {
149+
goto fail;
150+
}
151+
if (PyArray_SIZE(tmp1) > 0) {
152+
/* The input is not empty, so convert it to NPY_INTP. */
153+
lst = (PyArrayObject *)PyArray_ContiguousFromAny((PyObject *)tmp1,
154+
NPY_INTP, 1, 1);
155+
Py_DECREF(tmp1);
156+
if (lst == NULL) {
157+
/* Failed converting to NPY_INTP. */
158+
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
159+
PyErr_Clear();
160+
/* Deprecated 2024-08-02, NumPy 2.1 */
161+
if (DEPRECATE("Non-integer input passed to bincount. In a "
162+
"future version of NumPy, this will be an "
163+
"error. (Deprecated NumPy 2.1)") < 0) {
164+
goto fail;
165+
}
166+
}
167+
else {
168+
/* Failure was not a TypeError. */
169+
goto fail;
170+
}
171+
}
172+
}
173+
else {
174+
/* Got an empty list. */
175+
Py_DECREF(tmp1);
176+
}
177+
}
178+
134179
if (lst == NULL) {
135-
goto fail;
180+
lst = (PyArrayObject *)PyArray_ContiguousFromAny(list, NPY_INTP, 1, 1);
181+
if (lst == NULL) {
182+
goto fail;
183+
}
136184
}
137185
len = PyArray_SIZE(lst);
138186

numpy/_core/tests/test_deprecations.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,11 @@ class TestBincount(_DeprecationTestCase):
217217
def test_bincount_minlength(self):
218218
self.assert_deprecated(lambda: np.bincount([1, 2, 3], minlength=None))
219219

220+
# 2024-07-29, 2.1.0
221+
@pytest.mark.parametrize('badlist', [[0.5, 1.2, 1.5],
222+
['0', '1', '1']])
223+
def test_bincount_bad_list(self, badlist):
224+
self.assert_deprecated(lambda: np.bincount(badlist))
220225

221226

222227
class TestGeneratorSum(_DeprecationTestCase):

numpy/lib/tests/test_function_base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2848,6 +2848,11 @@ def test_empty_with_minlength(self):
28482848
y = np.bincount(x, minlength=5)
28492849
assert_array_equal(y, np.zeros(5, dtype=int))
28502850

2851+
@pytest.mark.parametrize('minlength', [0, 3])
2852+
def test_empty_list(self, minlength):
2853+
assert_array_equal(np.bincount([], minlength=minlength),
2854+
np.zeros(minlength, dtype=int))
2855+
28512856
def test_with_incorrect_minlength(self):
28522857
x = np.array([], dtype=int)
28532858
assert_raises_regex(TypeError,

0 commit comments

Comments
 (0)