Skip to content

Commit

Permalink
BUG: Fix string/bytes to complex assignment
Browse files Browse the repository at this point in the history
This was a regression, which happened because of discrepencies
between setting a single item from a string (which was not possible)
and casting from a string to array (which was possible before).
The seconnd code path changed, relying on the former behaviour, which
broke it. This fixes the issue by bringing the second branch in line.

Closes gh-16909
  • Loading branch information
seberg authored and charris committed Jul 20, 2020
1 parent 122065f commit 5cadc8f
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 4 deletions.
37 changes: 35 additions & 2 deletions numpy/core/src/multiarray/arraytypes.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,42 @@ static int
oop.real = NPY_NAN;
oop.imag = NPY_NAN;
}
else if (PyBytes_Check(op) || PyUnicode_Check(op)) {
/*
* Unlike most numeric conversion functions PyComplex_AsCComplex
* does not handle strings, so we have to use its constructor.
*/
PyObject *pycomplex, *args;
if (PyBytes_Check(op)) {
/* The complex constructor expects unicode */
PyObject *unicode;
unicode = PyUnicode_FromEncodedObject(op, NULL, NULL);
if (unicode == NULL) {
return -1;
}
args = PyTuple_Pack(1, unicode);
Py_DECREF(unicode);
}
else {
args = PyTuple_Pack(1, op);
}
if (args == NULL) {
return -1;
}
pycomplex = PyComplex_Type.tp_new(&PyComplex_Type, args, NULL);
Py_DECREF(args);
if (pycomplex == NULL) {
return -1;
}
oop = PyComplex_AsCComplex(pycomplex);
Py_DECREF(pycomplex);
if (error_converting(oop.real)) {
return -1;
}
}
else {
oop = PyComplex_AsCComplex (op);
if (PyErr_Occurred()) {
oop = PyComplex_AsCComplex(op);
if (error_converting(oop.real)) {
return -1;
}
}
Expand Down
23 changes: 23 additions & 0 deletions numpy/core/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,29 @@ def test_string_to_boolean_cast_errors(dtype, out_dtype):
with assert_raises(ValueError):
arr.astype(out_dtype)

@pytest.mark.parametrize("str_type", [str, bytes, np.str_, np.unicode_])
@pytest.mark.parametrize("scalar_type",
[np.complex64, np.complex128, np.clongdouble])
def test_string_to_complex_cast(str_type, scalar_type):
value = scalar_type(b"1+3j")
assert scalar_type(value) == 1+3j
assert np.array([value], dtype=object).astype(scalar_type)[()] == 1+3j
assert np.array(value).astype(scalar_type)[()] == 1+3j
arr = np.zeros(1, dtype=scalar_type)
arr[0] = value
assert arr[0] == 1+3j

@pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
def test_none_to_nan_cast(dtype):
# Note that at the time of writing this test, the scalar constructors
# reject None
arr = np.zeros(1, dtype=dtype)
arr[0] = None
assert np.isnan(arr)[0]
assert np.isnan(np.array(None, dtype=dtype))[()]
assert np.isnan(np.array([None], dtype=dtype))[0]
assert np.isnan(np.array(None).astype(dtype))[()]

def test_copyto_fromscalar():
a = np.arange(6, dtype='f4').reshape(2, 3)

Expand Down
5 changes: 3 additions & 2 deletions numpy/core/tests/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2430,9 +2430,10 @@ def test_pickle_module(self, protocol, val):
assert b'numpy.core.multiarray' in s

def test_object_casting_errors(self):
# gh-11993
# gh-11993 update to ValueError (see gh-16909), since strings can in
# principle be converted to complex, but this string cannot.
arr = np.array(['AAAAA', 18465886.0, 18465886.0], dtype=object)
assert_raises(TypeError, arr.astype, 'c8')
assert_raises(ValueError, arr.astype, 'c8')

def test_eff1d_casting(self):
# gh-12711
Expand Down

0 comments on commit 5cadc8f

Please sign in to comment.