Skip to content

Commit 3bbcb24

Browse files
committed
Broaden conversion rule scopes across numeric lattice
1 parent d242b1c commit 3bbcb24

6 files changed

Lines changed: 143 additions & 54 deletions

File tree

src/Convert/Convert.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ using ..C
1111
using ..Core
1212
using ..NumpyDates
1313

14-
using Dates: Date, Time, DateTime, Second, Millisecond, Microsecond, Nanosecond
14+
using Dates: Date, Time, DateTime, Period, Second, Millisecond, Microsecond, Nanosecond
1515

1616
import ..PythonCall:
1717
@pyconvert,

src/Convert/ctypes.jl

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,47 @@ const CTYPES_SIMPLE_TYPES = [
3737
function init_ctypes()
3838
for (t, T) in CTYPES_SIMPLE_TYPES
3939
isptr = endswith(t, "_p")
40-
isreal = !isptr
41-
isnumber = isreal
40+
isnumber = !isptr
4241
isfloat = t in ("float", "double")
43-
isint = isreal && !isfloat
42+
isint = isnumber && !isfloat
4443
isuint = isint && (startswith(t, "u") || t == "size_t")
4544

4645
name = "ctypes:c_$t"
4746
rule = pyconvert_rule_ctypessimplevalue{T,false}()
4847
saferule = pyconvert_rule_ctypessimplevalue{T,true}()
4948

50-
t == "char_p" && pyconvert_add_rule(name, Cstring, Cstring, saferule)
51-
t == "wchar_p" && pyconvert_add_rule(name, Cwstring, Cwstring, saferule)
52-
pyconvert_add_rule(name, T, T, saferule)
53-
isuint && pyconvert_add_rule(name, UInt, UInt, sizeof(T) sizeof(UInt) ? saferule : rule)
54-
isuint && pyconvert_add_rule(name, Int, Int, sizeof(T) < sizeof(Int) ? saferule : rule)
55-
isint &&
56-
!isuint &&
57-
pyconvert_add_rule(name, Int, Int, sizeof(T) sizeof(Int) ? saferule : rule)
58-
isint && pyconvert_add_rule(name, Integer, Integer, rule)
59-
isfloat && pyconvert_add_rule(name, Float64, Float64, saferule)
60-
isreal && pyconvert_add_rule(name, Real, Real, rule)
61-
isnumber && pyconvert_add_rule(name, Number, Number, rule)
62-
isptr && pyconvert_add_rule(name, Ptr, Ptr, saferule)
49+
if isnumber
50+
# Rules added later are tried first. Prefer the source's exact Julia type,
51+
# followed by increasingly general lossless representations, before allowing
52+
# conversion to floating-point and other number types.
53+
pyconvert_add_rule(name, Number, Number, rule)
54+
pyconvert_add_rule(name, Real, Number, rule)
55+
pyconvert_add_rule(name, AbstractFloat, Number, rule)
56+
if isint
57+
pyconvert_add_rule(name, Integer, Number, rule)
58+
pyconvert_add_rule(name, isuint ? Unsigned : Signed, Number, rule)
59+
if isuint
60+
pyconvert_add_rule(
61+
name,
62+
UInt,
63+
Number,
64+
sizeof(T) sizeof(UInt) ? saferule : rule,
65+
)
66+
else
67+
pyconvert_add_rule(
68+
name,
69+
Int,
70+
Number,
71+
sizeof(T) sizeof(Int) ? saferule : rule,
72+
)
73+
end
74+
elseif isfloat
75+
pyconvert_add_rule(name, Float64, Number, saferule)
76+
end
77+
pyconvert_add_rule(name, T, Number, saferule)
78+
elseif isptr
79+
pyconvert_add_rule(name, Ptr, Ptr, saferule)
80+
pyconvert_add_rule(name, T, Ptr, saferule)
81+
end
6382
end
6483
end

src/Convert/numpy.jl

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,39 @@ function init_numpy()
113113
rule = pyconvert_rule_numpysimplevalue{T,false}()
114114
saferule = pyconvert_rule_numpysimplevalue{T,true}()
115115

116-
isuint && pyconvert_add_rule(name, UInt, UInt, sizeof(T) sizeof(UInt) ? saferule : rule)
117-
isuint && pyconvert_add_rule(name, Int, Int, sizeof(T) < sizeof(Int) ? saferule : rule)
118-
isint &&
119-
!isuint &&
120-
pyconvert_add_rule(name, Int, Int, sizeof(T) sizeof(Int) ? saferule : rule)
121-
isint && pyconvert_add_rule(name, Integer, Integer, rule)
122-
isfloat && pyconvert_add_rule(name, Float64, Float64, saferule)
123-
isreal && pyconvert_add_rule(name, Real, Real, rule)
124-
iscomplex && pyconvert_add_rule(name, ComplexF64, ComplexF64, saferule)
125-
iscomplex && pyconvert_add_rule(name, Complex, Complex, rule)
126-
isnumber && pyconvert_add_rule(name, Number, Number, rule)
116+
if isnumber
117+
pyconvert_add_rule(name, Number, Number, rule)
118+
if isreal
119+
pyconvert_add_rule(name, Real, Number, rule)
120+
pyconvert_add_rule(name, AbstractFloat, Number, rule)
121+
else
122+
pyconvert_add_rule(name, Complex, Number, rule)
123+
end
124+
if isint
125+
pyconvert_add_rule(name, Integer, Number, rule)
126+
pyconvert_add_rule(name, isuint ? Unsigned : Signed, Number, rule)
127+
if isuint
128+
pyconvert_add_rule(
129+
name,
130+
UInt,
131+
Number,
132+
sizeof(T) sizeof(UInt) ? saferule : rule,
133+
)
134+
else
135+
pyconvert_add_rule(
136+
name,
137+
Int,
138+
Number,
139+
sizeof(T) sizeof(Int) ? saferule : rule,
140+
)
141+
end
142+
elseif isfloat
143+
pyconvert_add_rule(name, Float64, Number, saferule)
144+
elseif iscomplex
145+
pyconvert_add_rule(name, ComplexF64, Number, saferule)
146+
end
147+
pyconvert_add_rule(name, T, Number, saferule)
148+
end
127149
end
128150

129151
# datetime64

src/Convert/pyconvert.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -365,11 +365,11 @@ function init_pyconvert()
365365
pyconvert_add_rule("numbers:Integral", Number, Number, pyconvert_rule_int)
366366
pyconvert_add_rule("builtins:str", Symbol, Symbol, pyconvert_rule_str)
367367
pyconvert_add_rule("builtins:str", Char, Char, pyconvert_rule_str)
368-
pyconvert_add_rule("builtins:bytes", Vector{UInt8}, Vector{UInt8}, pyconvert_rule_bytes)
368+
pyconvert_add_rule("builtins:bytes", Vector{UInt8}, AbstractVector, pyconvert_rule_bytes)
369369
pyconvert_add_rule(
370370
"builtins:range",
371371
UnitRange{<:Integer},
372-
UnitRange{<:Integer},
372+
AbstractRange,
373373
pyconvert_rule_range,
374374
)
375375
pyconvert_add_rule(
@@ -380,24 +380,24 @@ function init_pyconvert()
380380
)
381381
pyconvert_add_rule("collections.abc:Iterable", Tuple, Tuple, pyconvert_rule_iterable)
382382
pyconvert_add_rule("collections.abc:Iterable", Pair, Pair, pyconvert_rule_iterable)
383-
pyconvert_add_rule("collections.abc:Iterable", Set, Set, pyconvert_rule_iterable)
383+
pyconvert_add_rule("collections.abc:Iterable", Set, AbstractSet, pyconvert_rule_iterable)
384384
pyconvert_add_rule(
385385
"collections.abc:Sequence",
386386
Vector,
387387
AbstractArray,
388388
pyconvert_rule_iterable,
389389
)
390390
pyconvert_add_rule("collections.abc:Sequence", Tuple, Tuple, pyconvert_rule_iterable)
391-
pyconvert_add_rule("collections.abc:Set", Set, Set, pyconvert_rule_iterable)
392-
pyconvert_add_rule("collections.abc:Mapping", Dict, Dict, pyconvert_rule_mapping)
391+
pyconvert_add_rule("collections.abc:Set", Set, AbstractSet, pyconvert_rule_iterable)
392+
pyconvert_add_rule("collections.abc:Mapping", Dict, AbstractDict, pyconvert_rule_mapping)
393393
pyconvert_add_rule(
394394
"datetime:timedelta",
395395
Millisecond,
396-
Millisecond,
396+
Period,
397397
pyconvert_rule_timedelta,
398398
)
399-
pyconvert_add_rule("datetime:timedelta", Second, Second, pyconvert_rule_timedelta)
400-
pyconvert_add_rule("datetime:timedelta", Nanosecond, Nanosecond, pyconvert_rule_timedelta)
399+
pyconvert_add_rule("datetime:timedelta", Second, Period, pyconvert_rule_timedelta)
400+
pyconvert_add_rule("datetime:timedelta", Nanosecond, Period, pyconvert_rule_timedelta)
401401
end
402402

403403
function init_pyconvert_canonical()

src/Wrap/Wrap.jl

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ include("PyPandasDataFrame.jl")
3333

3434
function __init__()
3535
priority = 0
36+
Convert.pyconvert_add_rule_high_priority(
37+
"pandas:DataFrame",
38+
PyPandasDataFrame,
39+
Any,
40+
pyconvert_rule_pandasdataframe,
41+
priority,
42+
)
43+
Convert.pyconvert_add_rule_high_priority(
44+
"pandas.api.extensions:ExtensionArray",
45+
PyList,
46+
Any,
47+
pyconvert_rule_sequence,
48+
priority,
49+
)
3650
Convert.pyconvert_add_rule_high_priority(
3751
"collections.abc:Sequence",
3852
PyList,
@@ -68,25 +82,7 @@ function __init__()
6882
pyconvert_rule_io,
6983
priority,
7084
)
71-
Convert.pyconvert_add_rule_high_priority(
72-
"pandas:DataFrame",
73-
PyPandasDataFrame,
74-
Any,
75-
pyconvert_rule_pandasdataframe,
76-
priority,
77-
)
78-
Convert.pyconvert_add_rule_high_priority(
79-
"pandas.api.extensions:ExtensionArray",
80-
PyList,
81-
Any,
82-
pyconvert_rule_sequence,
83-
priority,
84-
)
8585

86-
pyconvert_add_rule("<arraystruct>", Array, AbstractArray, pyconvert_rule_array)
87-
pyconvert_add_rule("<arrayinterface>", Array, AbstractArray, pyconvert_rule_array)
88-
pyconvert_add_rule("<array>", Array, AbstractArray, pyconvert_rule_array)
89-
pyconvert_add_rule("<buffer>", Array, AbstractArray, pyconvert_rule_array)
9086
pyconvert_add_rule("<arraystruct>", AbstractArray, AbstractArray, pyconvert_rule_array)
9187
pyconvert_add_rule("<arrayinterface>", AbstractArray, AbstractArray, pyconvert_rule_array)
9288
pyconvert_add_rule("<array>", AbstractArray, AbstractArray, pyconvert_rule_array)

test/Convert.jl

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,58 @@ end
5858
@test x4 == big(3)^1000
5959
end
6060

61+
@testitem "ctypes number lattice" begin
62+
ctypes = pyimport("ctypes")
63+
64+
x = ctypes.c_int32(12)
65+
@test pyconvert(Cint, x) === Cint(12)
66+
@test pyconvert(Int, x) === 12
67+
@test pyconvert(Signed, x) === Cint(12)
68+
@test pyconvert(Integer, x) === Cint(12)
69+
@test pyconvert(AbstractFloat, x) === 12.0
70+
@test pyconvert(Real, x) === Cint(12)
71+
@test pyconvert(Number, x) === Cint(12)
72+
@test pyconvert(Union{Int64,Float64}, x) === 12
73+
74+
y = ctypes.c_uint32(13)
75+
@test pyconvert(Cuint, y) === Cuint(13)
76+
@test pyconvert(UInt, y) === UInt(13)
77+
@test pyconvert(Unsigned, y) === Cuint(13)
78+
@test pyconvert(Integer, y) === Cuint(13)
79+
80+
z = ctypes.c_float(1.5)
81+
@test pyconvert(Cfloat, z) === Cfloat(1.5)
82+
@test pyconvert(Float64, z) === 1.5
83+
@test pyconvert(AbstractFloat, z) === Cfloat(1.5)
84+
@test pyconvert(Real, z) === Cfloat(1.5)
85+
@test pyconvert(Number, z) === Cfloat(1.5)
86+
@test pyconvert(Union{Int64,Float64}, z) === 1.5
87+
end
88+
89+
@testitem "numpy number lattice" setup=[Setup] begin
90+
if Setup.devdeps
91+
np = pyimport("numpy")
92+
93+
x = np.int32(12)
94+
@test pyconvert(Int32, x) === Int32(12)
95+
@test pyconvert(Int, x) === 12
96+
@test pyconvert(Signed, x) === Int32(12)
97+
@test pyconvert(Integer, x) === Int32(12)
98+
@test pyconvert(AbstractFloat, x) === 12.0
99+
@test pyconvert(Real, x) === Int32(12)
100+
@test pyconvert(Number, x) === Int32(12)
101+
@test pyconvert(Union{Int64,Float64}, x) === 12
102+
103+
y = np.float32(1.5)
104+
@test pyconvert(Float32, y) === Float32(1.5)
105+
@test pyconvert(Float64, y) === 1.5
106+
@test pyconvert(AbstractFloat, y) === Float32(1.5)
107+
@test pyconvert(Real, y) === Float32(1.5)
108+
@test pyconvert(Number, y) === Float32(1.5)
109+
@test pyconvert(Union{Int64,Float64}, y) === 1.5
110+
end
111+
end
112+
61113
@testitem "None → Nothing" begin
62114
x1 = pyconvert(Nothing, pybuiltins.None)
63115
@test x1 === nothing

0 commit comments

Comments
 (0)