Skip to content

Commit 2cf5cef

Browse files
author
Christopher Doris
committed
PyArray can wrap structs as tuples or named tuples
1 parent 19a71be commit 2cf5cef

File tree

3 files changed

+72
-4
lines changed

3 files changed

+72
-4
lines changed

docs/src/releasenotes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* When using JuliaCall from an interactive Python session, Julia is put into interactive
55
mode: `isinteractive()` is true, InteractiveUtils is loaded, and a nicer display is used.
66
* Wrapped Julia values now truncate their output when displayed via `_repr_mimebundle_`.
7+
* Numpy arrays with structured dtypes can now be converted to `PyArray`, provided the fields
8+
are aligned.
79
* Python named tuples can be converted to Julia named tuples.
810
* Bug fixes.
911

src/jlwrap/array.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ pytypestrdescr(::Type{T}) where {T} = get!(PYTYPESTRDESCR, T) do
250250
isempty(ts) && return ("", PyNULL)
251251
push!(
252252
flds,
253-
(nm isa Integer ? "f$(nm-1)" : string(nm), ds === nothing ? ts : ds),
253+
(nm isa Integer ? "f$(nm-1)" : string(nm), pyisnull(ds) ? ts : ds),
254254
)
255255
d = (i == n ? sizeof(T) : fieldoffset(T, i + 1)) - (fieldoffset(T, i) + sizeof(tp))
256256
@assert d 0

src/pywrap/PyArray.jl

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,70 @@ pyarray_typestrdescr_to_type(ts::String, descr::Py) = begin
262262
return Utils.StaticString{UInt32,sz}
263263
elseif etc == 'O'
264264
return UnsafePyObject
265+
elseif etc == 'V'
266+
pyisnull(descr) && error("not supported: void dtype with null descr")
267+
sz = parse(Int, ts[3:end])
268+
T = pyarray_descr_to_type(descr)
269+
sizeof(T) == sz || error("size mismatch: itemsize=$sz but sizeof(descr)=$(sizeof(T))")
270+
return T
271+
else
272+
error("not supported: dtype of kind: $(repr(etc))")
273+
end
274+
end
275+
276+
function pyarray_descr_to_type(descr::Py)
277+
fnames = Symbol[]
278+
foffsets = Int[]
279+
ftypes = DataType[]
280+
curoffset = 0
281+
for item in descr
282+
# get the name
283+
name = item[0]
284+
if pyistuple(name)
285+
name = name[0]
286+
end
287+
fname = Symbol(pyconvert(String, name))
288+
# get the shape
289+
if length(item) > 2
290+
shape = pyconvert(Vector{Int}, item[2])
291+
else
292+
shape = Int[]
293+
end
294+
# get the type
295+
descr2 = item[1]
296+
if pyisstr(descr2)
297+
typestr = pyconvert(String, descr2)
298+
# void entries are just padding to ignore
299+
if typestr[2] == 'V'
300+
curoffset += parse(Int, typestr[3:end]) * prod(shape)
301+
continue
302+
end
303+
ftype = pyarray_typestrdescr_to_type(typestr, PyNULL)
304+
else
305+
ftype = pyarray_descr_to_type(descr2)
306+
end
307+
# apply the shape
308+
for n in reverse(shape)
309+
ftype = NTuple{n,ftype}
310+
end
311+
# save the field
312+
push!(fnames, fname)
313+
push!(foffsets, curoffset)
314+
push!(ftypes, ftype)
315+
curoffset += sizeof(ftype)
316+
end
317+
# construct the tuple type and check its offsets and size
318+
# TODO: support non-aligned dtypes by packing them into a custom type and reinterpreting
319+
T = Tuple{ftypes...}
320+
for (i, o) in pairs(foffsets)
321+
fieldoffset(T, i) == o || error("not supported: dtype that is not aligned: $descr")
322+
end
323+
sizeof(T) == curoffset || error("not supported: dtype with end padding: $descr")
324+
# return the tuple type if the field names are f0, f1, ..., else return a named tuple
325+
if fnames == [Symbol(:f, i-1) for i in 1:length(fnames)]
326+
return T
265327
else
266-
error("type not supported: $ts")
328+
return NamedTuple{Tuple(fnames), T}
267329
end
268330
end
269331

@@ -380,9 +442,13 @@ function pyarray_get_R(src::PyArraySource_ArrayStruct)
380442
mod(size, 4) == 0 || error("unicode size must be a multiple of 4: $size")
381443
return Utils.StaticString{UInt32,div(size, 4)}
382444
elseif kind == 86 # V = void (should have descr)
383-
error("dtype not supported")
445+
hasdescr || error("not supported: void dtype with no descr")
446+
descr = pynew(incref(src.info.descr))
447+
T = pyarray_descr_to_type(descr)
448+
sizeof(T) == size || error("size mismatch: itemsize=$size but sizeof(descr)=$(sizeof(T))")
449+
return T
384450
else
385-
error("unexpected kind ($(Char(kind)))")
451+
error("not supported: dtype of kind: $(Char(kind))")
386452
end
387453
@assert false
388454
end

0 commit comments

Comments
 (0)