Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.22.0] - 2027-MM-DD

### Added

### Changed

### Deprecated

* Deprecated the upcast of `arr` by a Python numeric scalar in `dpnp.append` with `axis=None` (e.g. `int8` array and `1` giving `int64`) [#3125](https://github.com/IntelPython/dpnp/pull/3125)

### Removed

### Fixed

### Security

## [0.21.0] - 2026-MM-DD

This release achieves 100% compliance with Python Array API specification (revision [2025.12](https://data-apis.org/array-api/2025.12/)).
Expand Down
32 changes: 31 additions & 1 deletion dpnp/dpnp_iface_manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,16 @@ def append(arr, values, axis=None):
`append` does not occur in-place: a new array is allocated and
filled. If `axis` is ``None``, `out` is a flattened array.

Warnings
--------
When `axis` is ``None``, a Python numeric scalar in `values` is converted
to an array before the result dtype is chosen, so it can upcast `arr`
(e.g. appending ``1`` to an ``int8`` array gives ``int64``). This is
deprecated: a future release will use the Python scalar promotion rules
and keep ``int8``. A ``FutureWarning`` is emitted when the result dtype
will change. Wrap the scalar in a list to keep the current result, or
convert it to an array of the desired dtype.

See Also
--------
:obj:`dpnp.insert` : Insert elements into an array.
Expand Down Expand Up @@ -602,6 +612,11 @@ def append(arr, values, axis=None):
"""

dpnp.check_supported_arrays_type(arr)

# only exact Python int/float/complex are weak (not bool, numpy scalars)
is_pyscalar = axis is None and type(values) in (int, float, complex)
future_dtype = dpnp.result_type(arr, values) if is_pyscalar else None

if not dpnp.is_supported_array_type(values):
values = dpnp.array(
values, usm_type=arr.usm_type, sycl_queue=arr.sycl_queue
Expand All @@ -613,7 +628,22 @@ def append(arr, values, axis=None):
if values.ndim != 1:
values = dpnp.ravel(values)
axis = 0
return dpnp.concatenate((arr, values), axis=axis)
res = dpnp.concatenate((arr, values), axis=axis)

if is_pyscalar and res.dtype != future_dtype:
# deprecated in dpnp 0.22.0, following NumPy 2.6
warnings.warn(
"dpnp.append converted Python numeric scalars to arrays before "
f"choosing the result dtype, producing dtype {res.dtype}. This "
"behavior will change in a future release, these inputs will use "
f"dtype {future_dtype}, which may round values differently or "
"raise an error. Wrap the scalar in a list to keep the current "
"result, or convert it to an array of dtype "
f"{str(future_dtype)!r} for the future behavior.",
FutureWarning,
stacklevel=2,
)
return res


def array_split(ary, indices_or_sections, axis=0):
Expand Down
55 changes: 55 additions & 0 deletions dpnp/tests/test_manipulation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import itertools
import warnings

import numpy
import pytest
Expand All @@ -23,6 +24,7 @@
get_integer_float_dtypes,
get_unsigned_dtypes,
has_support_aspect64,
numpy_version,
)
from .tensor.helper import get_queue_or_skip
from .third_party.cupy import testing
Expand Down Expand Up @@ -174,6 +176,59 @@ def test_axis(self, dtype1, dtype2):
result = dpnp.append(ia, ib, axis=1)
assert_array_equal(result, expected)

@pytest.mark.parametrize(
"dtype, scalar",
[("i1", 1), ("u1", 300), ("u1", -1), ("i2", 1), ("u4", 1), ("u8", 1)]
+ [
# without fp64 an inexact arr keeps its dtype, so no warning there
pytest.param(
dt,
val,
marks=pytest.mark.skipif(
not has_support_aspect64(), reason="fp64 required"
),
)
for dt, val in [("f4", 1), ("f4", 1.5), ("c8", 1), ("c8", 1j)]
],
)
def test_scalar_promotion_deprecated(self, dtype, scalar):
a = numpy.ones(3, dtype=dtype)
ia = dpnp.array(a)

msg = rf"append converted .* will use dtype {numpy.dtype(dtype)},"
with pytest.warns(FutureWarning, match=msg) as record:
result = dpnp.append(ia, scalar)
assert record[0].filename == __file__

if numpy_version() >= "2.6.0.dev0":
with pytest.warns(FutureWarning, match=msg) as record:
expected = numpy.append(a, scalar)
else:
expected = numpy.append(a, scalar)
assert_dtype_allclose(result, expected)

@pytest.mark.parametrize(
"dtype, value",
[
("i8", 1),
("i1", 1.5),
("i4", 1j),
("i1", True),
("i1", numpy.int64(1)),
("f4", numpy.float64(1.5)),
("i1", [1]),
],
)
def test_scalar_promotion_not_deprecated(self, dtype, value):
a = numpy.ones(3, dtype=dtype)
ia = dpnp.array(a)

with warnings.catch_warnings():
warnings.simplefilter("error", FutureWarning)
result = dpnp.append(ia, value)
expected = numpy.append(a, value)
assert_dtype_allclose(result, expected)


class TestArraySplit:
@pytest.mark.parametrize("xp", [numpy, dpnp])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ def test_scalar_lhs(self, xp, dtype1, dtype2):
scalar = xp.dtype(dtype1).type(10).item()
return xp.append(scalar, xp.arange(20, dtype=dtype2))

@pytest.mark.filterwarnings(
"ignore:.*append converted Python numeric scalars:FutureWarning"
)
@testing.for_all_dtypes_combination(
names=["dtype1", "dtype2"], no_bool=True
)
Expand Down
Loading