diff --git a/CondaPkg.toml b/CondaPkg.toml index fb185c61..4e0c0e7a 100644 --- a/CondaPkg.toml +++ b/CondaPkg.toml @@ -14,5 +14,6 @@ version = ">=3.10,<4" [dev.deps] matplotlib = "" +numpy = "" pyside6 = "" python = "<3.14" diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index f063c191..299aedcc 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -355,22 +355,20 @@ class ArrayValue(AnyValue): @property def __array_interface__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlarray_array_interface))) - def __array__(self, dtype=None): + def __array__(self, dtype=None, copy=None): + import numpy # convert to an array-like object arr = self if not (hasattr(arr, "__array_interface__") or hasattr(arr, "__array_struct__")): + if copy is False: + raise ValueError("copy=False is not supported when collecting ArrayValue data") # the first attempt collects into an Array arr = self._jl_callmethod($(pyjl_methodnum(pyjlarray_array__array))) if not (hasattr(arr, "__array_interface__") or hasattr(arr, "__array_struct__")): # the second attempt collects into a PyObjectArray arr = self._jl_callmethod($(pyjl_methodnum(pyjlarray_array__pyobjectarray))) # convert to a numpy array if numpy is available - try: - import numpy - arr = numpy.array(arr, dtype=dtype) - except ImportError: - pass - return arr + return numpy.array(arr, dtype=dtype, copy=copy) def to_numpy(self, dtype=None, copy=True, order="K"): import numpy return numpy.array(self, dtype=dtype, copy=copy, order=order) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 56bf0e8e..52d93642 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -215,7 +215,7 @@ end end -@testitem "array" begin +@testitem "array" setup=[Setup] begin @testset "type" begin @test pyis(pytype(pyjl(fill(nothing))), PythonCall.pyjlarraytype) @test pyis(pytype(pyjl([1 2; 3 4])), PythonCall.pyjlarraytype) @@ -313,6 +313,42 @@ end @test pyjlvalue(x) == [0 2; 3 4] @test pyjlvalue(y) == [1 2; 3 4] end + @testset "__array__" begin + if Setup.devdeps + np = pyimport("numpy") + + numeric = pyjl(Float64[1, 2, 3]) + numeric_array = numeric.__array__() + @test pyisinstance(numeric_array, np.ndarray) + @test pyconvert(Vector{Float64}, numeric_array) == [1.0, 2.0, 3.0] + + numeric_no_copy = numeric.__array__(copy=false) + numeric_data = pyjlvalue(numeric) + numeric_data[1] = 42.0 + @test pyconvert(Vector{Float64}, numeric_no_copy) == [42.0, 2.0, 3.0] + + string_array = pyjl(["a", "b"]) + string_result = string_array.__array__() + @test pyisinstance(string_result, np.ndarray) + @test pyconvert(Vector{String}, pybuiltins.list(string_result)) == ["a", "b"] + + err = try + string_array.__array__(copy=false) + nothing + catch err + err + end + @test err !== nothing + @test err isa PythonCall.PyException + @test pyis(err._t, pybuiltins.ValueError) + @test occursin( + "copy=False is not supported when collecting ArrayValue data", + sprint(showerror, err), + ) + else + @test_skip Setup.devdeps + end + end @testset "array_interface" begin x = pyjl(Float32[1 2 3; 4 5 6]).__array_interface__ @test pyisinstance(x, pybuiltins.dict)