Skip to content

Commit 4b89ee7

Browse files
author
Christopher Doris
committed
tweak pyconvert
1 parent 43d2a14 commit 4b89ee7

File tree

10 files changed

+71
-82
lines changed

10 files changed

+71
-82
lines changed

docs/src/releasenotes.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Release Notes
22

33
## Unreleased
4-
* `pyconvert_add_rule` is now documented. The separator of the first argument has changed
5-
from `/` to `:` (not breaking because it was previously undocumented).
4+
* `pyconvert_add_rule` is now documented. Its semantics have changed, including the
5+
separator of the first argument from `/` to `:` (not breaking because it was previously
6+
undocumented).
67

78
## v0.6.1 (2022-02-21)
89
* Conversions from simple ctypes types, e.g. `ctypes.c_float` to `Cfloat`.

src/concrete/bool.jl

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,10 @@ pybool_asbool(x) =
2323

2424
function pyconvert_rule_bool(::Type{T}, x::Py) where {T<:Number}
2525
val = pybool_asbool(x)
26+
pydel!(x)
2627
if T in (Bool, Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128, BigInt)
2728
pyconvert_return(T(val))
2829
else
2930
pyconvert_tryconvert(T, val)
3031
end
3132
end
32-
33-
pyconvert_rule_fast(::Type{Bool}, x::Py) =
34-
if pyisTrue(x)
35-
pyconvert_return(true)
36-
elseif pyisFalse(x)
37-
pyconvert_return(false)
38-
else
39-
pyconvert_unconverted()
40-
end

src/concrete/bytes.jl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,3 @@ end
3535

3636
pyconvert_rule_bytes(::Type{Vector{UInt8}}, x::Py) = pyconvert_return(copy(pybytes_asvector(x)))
3737
pyconvert_rule_bytes(::Type{Base.CodeUnits{UInt8,String}}, x::Py) = pyconvert_return(codeunits(pybytes_asUTF8string(x)))
38-
39-
pyconvert_rule_fast(::Type{Vector{UInt8}}, x::Py) = pyisbytes(x) ? pyconvert_rule_bytes(Vector{UInt8}, x) : pyconvert_unconverted()
40-
pyconvert_rule_fast(::Type{Base.CodeUnits{UInt8,String}}, x::Py) = pyisbytes(x) ? pyconvert_rule_bytyes(Base.CodeUnits{UInt8,String}, x) : pyconvert_unconverted()

src/concrete/complex.jl

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,3 @@ function pyconvert_rule_complex(::Type{T}, x::Py) where {T<:Number}
3131
pyconvert_tryconvert(T, val)
3232
end
3333
end
34-
35-
pyconvert_rule_fast(::Type{Complex{Float64}}, x::Py) =
36-
if pyiscomplex(x)
37-
pyconvert_return(pycomplex_ascomplex(x))
38-
else
39-
pyconvert_unconverted()
40-
end

src/concrete/float.jl

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pyfloat_asdouble(x) = errcheck_ambig(@autopy x C.PyFloat_AsDouble(getptr(x_)))
1616

1717
function pyconvert_rule_float(::Type{T}, x::Py) where {T<:Number}
1818
val = pyfloat_asdouble(x)
19+
pydel!(x)
1920
if T in (Float16, Float32, Float64, BigFloat)
2021
pyconvert_return(T(val))
2122
else
@@ -26,24 +27,21 @@ end
2627
# NaN is sometimes used to represent missing data of other types
2728
# so we allow converting it to Nothing or Missing
2829
function pyconvert_rule_float(::Type{Nothing}, x::Py)
29-
if isnan(pyfloat_asdouble(x))
30+
val = pyfloat_asdouble(x)
31+
pydel!(x)
32+
if isnan(val)
3033
pyconvert_return(nothing)
3134
else
3235
pyconvert_unconverted()
3336
end
3437
end
3538

3639
function pyconvert_rule_float(::Type{Missing}, x::Py)
37-
if isnan(pyfloat_asdouble(x))
40+
val = pyfloat_asdouble(x)
41+
pydel!(x)
42+
if isnan(val)
3843
pyconvert_return(missing)
3944
else
4045
pyconvert_unconverted()
4146
end
4247
end
43-
44-
pyconvert_rule_fast(::Type{Float64}, x::Py) =
45-
if pyisfloat(x)
46-
pyconvert_return(pyfloat_asdouble(x))
47-
else
48-
pyconvert_unconverted()
49-
end

src/concrete/int.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pyconvert_rule_int(::Type{T}, x::Py) where {T<:Number} = begin
3939
v = T <: Unsigned ? C.PyLong_AsUnsignedLongLong(getptr(x)) : C.PyLong_AsLongLong(getptr(x))
4040
if !iserrset_ambig(v)
4141
# success
42+
pydel!(x)
4243
return pyconvert_tryconvert(T, v)
4344
elseif errmatches(pybuiltins.OverflowError)
4445
# overflows Clonglong or Culonglong
@@ -59,17 +60,20 @@ pyconvert_rule_int(::Type{T}, x::Py) where {T<:Number} = begin
5960
typemin(typeof(v)) typemin(T) &&
6061
typemax(T) typemax(typeof(v))
6162
# definitely overflows S, give up now
63+
pydel!(x)
6264
return pyconvert_unconverted()
6365
else
6466
# try converting -> int -> str -> BigInt -> T
6567
x_int = pyint(x)
68+
pydel!(x)
6669
x_str = pystr(String, x_int)
6770
pydel!(x_int)
6871
v = parse(BigInt, x_str)
6972
return pyconvert_tryconvert(T, v)
7073
end
7174
else
7275
# other error
76+
pydel!(x)
7377
pythrow()
7478
end
7579
end

src/concrete/none.jl

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,3 @@ pyisnone(x) = pyis(x, pybuiltins.None)
22

33
pyconvert_rule_none(::Type{Nothing}, x::Py) = pyconvert_return(nothing)
44
pyconvert_rule_none(::Type{Missing}, x::Py) = pyconvert_return(missing)
5-
6-
pyconvert_rule_fast(::Type{Nothing}, x::Py) =
7-
if pyisnone(x)
8-
pyconvert_return(nothing)
9-
else
10-
pyconvert_unconverted()
11-
end
12-
13-
pyconvert_rule_fast(::Type{Missing}, x::Py) =
14-
if pyisnone(x)
15-
pyconvert_return(missing)
16-
else
17-
pyconvert_unconverted()
18-
end

src/concrete/range.jl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,3 @@ function pyconvert_rule_range(::Type{R}, x::Py, ::Type{UnitRange{T0}}=Utils._typ
3131
T2 = Utils._promote_type_bounded(T0, typeof(a′), typeof(c′), T1)
3232
pyconvert_return(UnitRange{T2}(a′, c′))
3333
end
34-
35-
pyconvert_rule_fast(::Type{T}, x::Py) where {T<:StepRange} = pyisrange(x) ? pyconvert_rule_range(T, x) : pyconvert_unconverted()
36-
pyconvert_rule_fast(::Type{T}, x::Py) where {T<:UnitRange} = pyisrange(x) ? pyconvert_rule_range(T, x) : pyconvert_unconverted()

src/concrete/str.jl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,3 @@ pyconvert_rule_str(::Type{Char}, x::Py) = begin
3535
end
3636

3737
pyisstr(x) = pytypecheckfast(x, C.Py_TPFLAGS_UNICODE_SUBCLASS)
38-
39-
pyconvert_rule_fast(::Type{String}, x::Py) = pyisstr(x) ? pyconvert_rule_str(String, x) : pyconvert_unconverted()
40-
pyconvert_rule_fast(::Type{Symbol}, x::Py) = pyisstr(x) ? pyconvert_rule_str(Symbol, x) : pyconvert_unconverted()
41-
pyconvert_rule_fast(::Type{Char}, x::Py) = pyisstr(x) ? pyconvert_rule_str(Char, x) : pyconvert_unconverted()

src/convert.jl

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -63,39 +63,41 @@ Other priorities are reserved for internal use.
6363
6464
### Optional optimization (for experts)
6565
66-
If `func(S, x::Py)` makes a successful conversion, it may call `pydel!(x)` if safe to do so.
67-
This means that the returned answer must not reference `x` in any way.
66+
The `func` is given the only reference to `x`. This means it may call `pydel!(x)` when done
67+
with `x` provided `x` is not referenced anywhere else, such as in the return value. See
68+
[`pydel!`](@ref).
6869
6970
If you are returning a wrapper type, such as `PyList(x)`, then the object `x` should be
7071
duplicated first, as in `Py(x)`. It is recommended to force this in the inner constructor
7172
of the wrapper type.
72-
73-
If the conversion is not successful, it **must not** call `pydel!(x)`.
7473
"""
7574
function pyconvert_add_rule(pytypename::String, type::Type, func::Function, priority::PyConvertPriority=PYCONVERT_PRIORITY_NORMAL)
7675
@nospecialize type func
7776
push!(get!(Vector{PyConvertRule}, PYCONVERT_RULES, pytypename), PyConvertRule(type, func, priority))
7877
return
7978
end
8079

81-
if false
82-
# this scheme returns either the result or Unconverted()
80+
# Alternative ways to represent the result of conversion.
81+
if true
82+
# Returns either the result or Unconverted().
8383
struct Unconverted end
84-
pyconvert_return(x) = x
85-
pyconvert_unconverted() = Unconverted()
86-
pyconvert_returntype(::Type{T}) where {T} = Union{T,Unconverted}
87-
pyconvert_isunconverted(r) = r === Unconverted()
88-
pyconvert_result(::Type{T}, r) where {T} = r::T
89-
elseif true
90-
# this scheme stores the result in PYCONVERT_RESULT
84+
@inline pyconvert_return(x) = x
85+
@inline pyconvert_unconverted() = Unconverted()
86+
@inline pyconvert_returntype(::Type{T}) where {T} = Union{T,Unconverted}
87+
@inline pyconvert_isunconverted(r) = r === Unconverted()
88+
@inline pyconvert_result(::Type{T}, r) where {T} = r::T
89+
elseif false
90+
# Stores the result in PYCONVERT_RESULT.
91+
# This is global state, probably best avoided.
9192
const PYCONVERT_RESULT = Ref{Any}(nothing)
92-
pyconvert_return(x) = (PYCONVERT_RESULT[] = x; true)
93-
pyconvert_unconverted() = false
94-
pyconvert_returntype(::Type{T}) where {T} = Bool
95-
pyconvert_isunconverted(r::Bool) = !r
96-
pyconvert_result(::Type{T}, r::Bool) where {T} = (ans = PYCONVERT_RESULT[]::T; PYCONVERT_RESULT[] = nothing; ans)
93+
@inline pyconvert_return(x) = (PYCONVERT_RESULT[] = x; true)
94+
@inline pyconvert_unconverted() = false
95+
@inline pyconvert_returntype(::Type{T}) where {T} = Bool
96+
@inline pyconvert_isunconverted(r::Bool) = !r
97+
@inline pyconvert_result(::Type{T}, r::Bool) where {T} = (ans = PYCONVERT_RESULT[]::T; PYCONVERT_RESULT[] = nothing; ans)
9798
else
98-
# same as the previous scheme, but with special handling for bits types
99+
# Same as the previous scheme, but with special handling for bits types.
100+
# This is global state, probably best avoided.
99101
const PYCONVERT_RESULT = Ref{Any}(nothing)
100102
const PYCONVERT_RESULT_ISBITS = Ref{Bool}(false)
101103
const PYCONVERT_RESULT_TYPE = Ref{Type}(Union{})
@@ -112,9 +114,9 @@ else
112114
end
113115
return true
114116
end
115-
pyconvert_unconverted() = false
116-
pyconvert_returntype(::Type{T}) where {T} = Bool
117-
pyconvert_isunconverted(r::Bool) = !r
117+
@inline pyconvert_unconverted() = false
118+
@inline pyconvert_returntype(::Type{T}) where {T} = Bool
119+
@inline pyconvert_isunconverted(r::Bool) = !r
118120
function pyconvert_result(::Type{T}, r::Bool) where {T}
119121
if isbitstype(T)
120122
if sizeof(T) PYCONVERT_RESULT_BITSLEN
@@ -271,40 +273,63 @@ end
271273

272274
pyconvert_fix(::Type{T}, func) where {T} = x -> func(T, x)
273275

274-
const PYCONVERT_RULES_CACHE = Dict{C.PyPtr, Dict{Type, Vector{Function}}}()
276+
const PYCONVERT_RULES_CACHE = Dict{Type, Dict{C.PyPtr, Vector{Function}}}()
277+
278+
@generated pyconvert_rules_cache(::Type{T}) where {T} = get!(Dict{C.PyPtr, Vector{Function}}, PYCONVERT_RULES_CACHE, T)
275279

276280
function pyconvert_rule_fast(::Type{T}, x::Py) where {T}
277281
if T isa Union
278282
a = pyconvert_rule_fast(T.a, x) :: pyconvert_returntype(T.a)
279283
pyconvert_isunconverted(a) || return a
280284
b = pyconvert_rule_fast(T.b, x) :: pyconvert_returntype(T.b)
281285
pyconvert_isunconverted(b) || return b
286+
elseif (T == Nothing) | (T == Missing)
287+
pyisnone(x) && return pyconvert_return(T())
288+
elseif (T == Bool)
289+
pyisFalse(x) && return pyconvert_return(false)
290+
pyisTrue(x) && return pyconvert_return(true)
291+
elseif (T == Int) | (T == BigInt)
292+
pyisint(x) && return pyconvert_rule_int(T, Py(x))
293+
elseif (T == Float64)
294+
pyisfloat(x) && return pyconvert_return(T(pyfloat_asdouble(x)))
295+
elseif (T == ComplexF64)
296+
pyiscomplex(x) && return pyconvert_return(T(pycomplex_ascomplex(x)))
297+
elseif (T == String) | (T == Char) | (T == Symbol)
298+
pyisstr(x) && return pyconvert_rule_str(T, Py(x))
299+
elseif (T == Vector{UInt8}) | (T == Base.CodeUnits{UInt8,String})
300+
pyisbytes(x) && return pyconvert_rule_bytes(T, Py(x))
301+
elseif (T <: StepRange) | (T <: UnitRange)
302+
pyisrange(x) && return pyconvert_rule_range(T, Py(x))
282303
end
283304
pyconvert_unconverted()
284305
end
285306

286307
function pytryconvert(::Type{T}, x_) where {T}
287308
# Copy the input
288309
x = Py(x_)
310+
289311
# We can optimize the conversion for some types by overloading pytryconvert_fast.
290312
# It MUST give the same results as via the slower route using rules.
291-
ans = pyconvert_rule_fast(T, x) :: pyconvert_returntype(T)
292-
pyconvert_isunconverted(ans) || return ans
313+
ans1 = pyconvert_rule_fast(T, x) :: pyconvert_returntype(T)
314+
pyconvert_isunconverted(ans1) || (pydel!(x); return ans1)
315+
293316
# get rules from the cache
294317
# TODO: we should hold weak references and clear the cache if types get deleted
295318
tptr = C.Py_Type(getptr(x))
296-
trules = get!(Dict{Type, Vector{PyConvertRule}}, PYCONVERT_RULES_CACHE, tptr)
297-
if !haskey(trules, T)
319+
trules = pyconvert_rules_cache(T)
320+
rules = get!(trules, tptr) do
298321
t = pynew(incref(tptr))
299-
trules[T] = pyconvert_get_rules(T, t)
322+
ans = pyconvert_get_rules(T, t)::Vector{Function}
300323
pydel!(t)
324+
ans
301325
end
302-
rules = trules[T]
326+
303327
# apply the rules
304328
for rule in rules
305-
ans = rule(x) :: pyconvert_returntype(T)
306-
pyconvert_isunconverted(ans) || return ans
329+
ans2 = rule(Py(x)) :: pyconvert_returntype(T)
330+
pyconvert_isunconverted(ans2) || (pydel!(x); return ans2)
307331
end
332+
308333
# if no rule succeeded, assume nothing references x
309334
pydel!(x)
310335
return pyconvert_unconverted()

0 commit comments

Comments
 (0)