Repository: dask/dask

Issue:
BUG: `array.asarray` does not respect `dtype` arg
**Describe the issue**:

`dask.array.asarray` does not respect the `dtype` argument.

**Minimal Complete Verifiable Example**:

```python
>>> import numpy as np
>>> import dask.array as da
>>> Zm = da.asarray([[1, 2, 3]]) 
>>> Zm
dask.array<array, shape=(1, 3), dtype=int64, chunksize=(1, 3), chunktype=numpy.ndarray>
>>> Z = da.asarray(Zm, dtype=da.float64)
>>> Z
dask.array<array, shape=(1, 3), dtype=int64, chunksize=(1, 3), chunktype=numpy.ndarray>
>>> Z.compute().dtype
dtype('int64')
# same issue is present with `np` dtypes directly
>>> Z = da.asarray(Zm, dtype=np.float64)
>>> Z
dask.array<array, shape=(1, 3), dtype=int64, chunksize=(1, 3), chunktype=numpy.ndarray>
>>> Z.compute().dtype
dtype('int64')
```

**Anything else we need to know?**:

**Environment**:

- Dask version: 2024.8.0+3.g65270980
- Python version: 3.12.4
- Operating System: Ubuntu
- Install method (conda, pip, source): `python -m pip install git+https://github.com/dask/dask.git`


Discussion:
GPUtester: Can one of the admins verify this patch?

Admins can comment `ok to test` to allow this one PR to run or `add to allowlist` to allow all future PRs from the same author to run.
lucascolley: cc @jsignell @jakirkham @jrbourbeau from gh-8106
lucascolley: Hmm, it looks like `from_array` does not accept Dask arrays, redirecting to `asarray`, but in `asarray` `dtype` and `order` are ignored. I suppose we want to coerce the dtype and order from `asarray` then.

Should I try using `Array.astype`? And perhaps we don't need to allow changing order?
lucascolley: I've hopefully fixed the case of `Array` input with `dtype`. Otherwise, I have added the warning:

```
.. warning::
        `dtype` is ignored if `a` has the attribute ``to_dask_array`` or
        is a list or tuple of `Array`'s.
        `order` is ignored if `a` is an `Array`, has the attribute ``to_dask_array``,
        or is a list or tuple of `Array`'s.
```
lucascolley: Currently the warning is below Examples on the docs pages. Not sure if it is possible to move upwards while keeping the admonition.

Patch:
diff --git a/dask/array/core.py b/dask/array/core.py
index b710fe491..3009dcb11 100644
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -4524,6 +4524,13 @@ def retrieve_from_ooc(
     return load_dsk
 
 
+def _as_dtype(a, dtype):
+    if dtype is None:
+        return a
+    else:
+        return a.astype(dtype)
+
+
 def asarray(
     a, allow_unknown_chunksizes=False, dtype=None, order=None, *, like=None, **kwargs
 ):
@@ -4573,16 +4580,22 @@ def asarray(
     >>> y = [[1, 2, 3], [4, 5, 6]]
     >>> da.asarray(y)
     dask.array<array, shape=(2, 3), dtype=int64, chunksize=(2, 3), chunktype=numpy.ndarray>
+
+    .. warning::
+        `order` is ignored if `a` is an `Array`, has the attribute ``to_dask_array``,
+        or is a list or tuple of `Array`'s.
     """
     if like is None:
         if isinstance(a, Array):
-            return a
+            return _as_dtype(a, dtype)
         elif hasattr(a, "to_dask_array"):
-            return a.to_dask_array()
+            return _as_dtype(a.to_dask_array(), dtype)
         elif type(a).__module__.split(".")[0] == "xarray" and hasattr(a, "data"):
-            return asarray(a.data)
+            return _as_dtype(asarray(a.data, order=order), dtype)
         elif isinstance(a, (list, tuple)) and any(isinstance(i, Array) for i in a):
-            return stack(a, allow_unknown_chunksizes=allow_unknown_chunksizes)
+            return _as_dtype(
+                stack(a, allow_unknown_chunksizes=allow_unknown_chunksizes), dtype
+            )
         elif not isinstance(getattr(a, "shape", None), Iterable):
             a = np.asarray(a, dtype=dtype, order=order)
     else:
@@ -4641,16 +4654,20 @@ def asanyarray(a, dtype=None, order=None, *, like=None, inline_array=False):
     >>> y = [[1, 2, 3], [4, 5, 6]]
     >>> da.asanyarray(y)
     dask.array<array, shape=(2, 3), dtype=int64, chunksize=(2, 3), chunktype=numpy.ndarray>
+
+    .. warning::
+        `order` is ignored if `a` is an `Array`, has the attribute ``to_dask_array``,
+        or is a list or tuple of `Array`'s.
     """
     if like is None:
         if isinstance(a, Array):
-            return a
+            return _as_dtype(a, dtype)
         elif hasattr(a, "to_dask_array"):
-            return a.to_dask_array()
+            return _as_dtype(a.to_dask_array(), dtype)
         elif type(a).__module__.split(".")[0] == "xarray" and hasattr(a, "data"):
-            return asanyarray(a.data)
+            return _as_dtype(asarray(a.data, order=order), dtype)
         elif isinstance(a, (list, tuple)) and any(isinstance(i, Array) for i in a):
-            return stack(a)
+            return _as_dtype(stack(a), dtype)
         elif not isinstance(getattr(a, "shape", None), Iterable):
             a = np.asanyarray(a, dtype=dtype, order=order)
     else:


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
index 70eeb0a9f..d975cfced 100644
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -2704,6 +2704,18 @@ def test_asarray(asarray):
     assert_eq(asarray(y), x)
 
 
+@pytest.mark.parametrize("asarray", [da.asarray, da.asanyarray])
+def test_asarray_array_dtype(asarray):
+    # test array input
+    x = asarray([1, 2])
+    assert_eq(asarray(x, dtype=da.float32), np.asarray(x, dtype=np.float32))
+
+    x = asarray(x, dtype=da.float64)
+    assert x.dtype == da.float64
+    x = asarray(x, dtype=da.int32)
+    assert x.dtype == da.int32
+
+
 @pytest.mark.parametrize("asarray", [da.asarray, da.asanyarray])
 def test_asarray_dask_dataframe(asarray):
     # https://github.com/dask/dask/issues/3885