Skip to content

Commit 2d6d898

Browse files
author
Christopher Doris
committed
remove getpy, now overload Py directly
1 parent 77698e1 commit 2d6d898

25 files changed

+85
-142
lines changed

docs/src/conversion-to-python.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ See [here](@ref julia-wrappers) for an explanation of the `juliacall.*Value` wra
3636

3737
## [Custom rules](@id jl2py-conversion-custom)
3838

39-
You may define a new conversion rule for your new type `T` by overloading `getpy(::T)` and
40-
possibly also `ispy(::T)` if it wraps a Python object.
39+
You may define a new conversion rule for your new type `T` by overloading `Py(::T)`.
40+
41+
If `T` is a wrapper type (such as `PyList`) where `Py(x)` simply returns the stored Python
42+
object, then also define `ispy(::T) = true`.
4143

4244
```@docs
43-
PythonCall.getpy
4445
PythonCall.ispy
4546
```
4647

docs/src/pythoncall-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ end
208208
ispy(x::MyType) = true
209209

210210
# Says how to access the underlying Python object.
211-
getpy(x::MyType) = x.py
211+
Py(x::MyType) = x.py
212212
```
213213

214214
## `@py` and `@pyconst`

docs/src/releasenotes.md

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

33
## Unreleased
4+
* **Breaking.** Removes `getpy`: you may now overload `Py` directly, which now need not
5+
always return a new object (e.g. for singletons or wrappers).
6+
* **Breaking.** Conversion rules no longer take a new object every time.
47
* **Breaking.** Improved Tables-interface support for `PyPandasDataFrame`: better inferred
58
column types; better handling of non-string column names; columns are usually wrappers
69
(`PyArray` or `PyList`). Constructor arguments have changed. Dict methods have been

src/Py.jl

Lines changed: 30 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pyisnull(x) = getptr(x) == C.PyNULL
2323
2424
Get the underlying pointer from the Python object `x`.
2525
"""
26-
getptr(x) = getptr(getpy(x)::Py)
26+
getptr(x) = ispy(x) ? getptr(Py(x)::Py) : throw(MethodError(getptr, (x,)))
2727

2828
"""
2929
Py(x)
@@ -36,10 +36,6 @@ Such an object supports attribute access (`obj.attr`), indexing (`obj[idx]`), ca
3636
(`obj(arg1, arg2)`), iteration (`for x in obj`), arithmetic (`obj + obj2`) and comparison
3737
(`obj > obj2`), among other things. These operations convert all their arguments to `Py` and
3838
return `Py`.
39-
40-
!!! warning
41-
42-
Do not overload this function. To define a new conversion, overload [`getpy`](@ref).
4339
"""
4440
mutable struct Py
4541
ptr :: C.PyPtr
@@ -56,7 +52,6 @@ function py_finalizer(x::Py)
5652
end
5753

5854
ispy(::Py) = true
59-
getpy(x::Py) = x
6055
getptr(x::Py) = getfield(x, :ptr)
6156

6257
setptr!(x::Py, ptr::C.PyPtr) = (setfield!(x, :ptr, ptr); x)
@@ -85,6 +80,8 @@ const PyNULL = pynew()
8580

8681
pynew(ptr::C.PyPtr) = setptr!(pynew(), ptr)
8782

83+
pynew(x::Py) = pynew(incref(getptr(x)))
84+
8885
"""
8986
pycopy!(dst::Py, src)
9087
@@ -97,7 +94,7 @@ the top level then `pycopy!(x, pything())` inside `__init__()`.
9794
9895
Assumes `dst` is NULL, otherwise a memory leak will occur.
9996
"""
100-
pycopy!(dst, src) = GC.@preserve src setptr!(dst, incref(getptr(src)))
97+
pycopy!(dst::Py, src) = GC.@preserve src setptr!(dst, incref(getptr(src)))
10198

10299
"""
103100
pydel!(x::Py)
@@ -120,76 +117,44 @@ function pydel!(x::Py)
120117
ptr = getptr(x)
121118
if ptr != C.PyNULL
122119
C.Py_DecRef(ptr)
120+
setptr!(x, C.PyNULL)
123121
end
124-
pystolen!(x)
125-
end
126-
127-
function pystolen!(x::Py)
128-
setptr!(x, C.PyNULL)
129122
push!(PYNULL_CACHE, x)
130-
nothing
123+
return
131124
end
132125

133126
macro autopy(args...)
134127
vs = args[1:end-1]
135128
ts = [Symbol(v, "_") for v in vs]
136129
body = args[end]
137-
ans = gensym("ans")
130+
# ans = gensym("ans")
138131
esc(quote
139-
$([:($t = $ispy($v) ? $v : $Py($v)) for (t, v) in zip(ts, vs)]...)
140-
$ans = $body
141-
$([:($ispy($v) || $pydel!($t)) for (t, v) in zip(ts, vs)]...)
142-
$ans
132+
# $([:($t = $ispy($v) ? $v : $Py($v)) for (t, v) in zip(ts, vs)]...)
133+
# $ans = $body
134+
# $([:($ispy($v) || $pydel!($t)) for (t, v) in zip(ts, vs)]...)
135+
# $ans
136+
$([:($t = $Py($v)) for (t, v) in zip(ts, vs)]...)
137+
$body
143138
end)
144139
end
145140

146-
struct NewPy
147-
py::Py
148-
NewPy(py::Py) = new(py)
149-
end
150-
151-
Py(x::Py) = GC.@preserve x pynew(incref(getptr(x))) # copy, because Py must always return a new object
152-
Py(x::NewPy) = x.py
153-
Py(x) = Py(getpy(x)::Union{Py,NewPy})
154-
155-
"""
156-
getpy(x)
157-
158-
Convert `x` to a `Py`.
159-
160-
Overload this function (not [`Py`](@ref)) to define a new conversion to Python.
161-
162-
If `x` is a simple wrapper around a Python object (such as [`PyList`](@ref) or
163-
[`PyDict`](@ref)) then `getpy(x)` should return the Python object. You should also define
164-
`ispy(x) = true`. This means that when `x` is passed back to Python, the underlying object
165-
is used directly.
166-
167-
### Optional optimization (for experts)
168-
169-
If [`ispy(x)`](@ref) is false and the returned Julia object `ans` is not referenced anywhere
170-
else, in the sense that [`pydel!(ans)`](@ref) would be safe, you may instead return
171-
`NewPy(ans)`.
172-
173-
This can avoid the Julia garbage collector in performance-critical code.
174-
175-
If [`ispy(x)`](@ref) is true, you **must** return a [`Py`](@ref).
176-
"""
177-
getpy(x) = ispy(x) ? throw(MethodError(getpy, (x,))) : NewPy(pyjl(x))
178-
getpy(x::Nothing) = pybuiltins.None
179-
getpy(x::Bool) = NewPy(pybool(x))
180-
getpy(x::Union{String, SubString{String}, Char}) = NewPy(pystr(x))
181-
getpy(x::Base.CodeUnits{UInt8, String}) = NewPy(pybytes(x))
182-
getpy(x::Base.CodeUnits{UInt8, SubString{String}}) = NewPy(pybytes(x))
183-
getpy(x::Tuple) = NewPy(pytuple_fromiter(x))
184-
getpy(x::Pair) = NewPy(pytuple_fromiter(x))
185-
getpy(x::Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}) = NewPy(pyint(x))
186-
getpy(x::Rational{<:Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}}) = NewPy(pyfraction(x))
187-
getpy(x::Union{Float16,Float32,Float64}) = NewPy(pyfloat(x))
188-
getpy(x::Complex{<:Union{Float16,Float32,Float64}}) = NewPy(pycomplex(x))
189-
getpy(x::AbstractRange{<:Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}}) = NewPy(pyrange_fromrange(x))
190-
getpy(x::Date) = NewPy(pydate(x))
191-
getpy(x::Time) = NewPy(pytime(x))
192-
getpy(x::DateTime) = NewPy(pydatetime(x))
141+
Py(x::Py) = x
142+
Py(x::Nothing) = pybuiltins.None
143+
Py(x::Bool) = x ? pybuiltins.True : pybuiltins.False
144+
Py(x::Union{String, SubString{String}, Char}) = pystr(x)
145+
Py(x::Base.CodeUnits{UInt8, String}) = pybytes(x)
146+
Py(x::Base.CodeUnits{UInt8, SubString{String}}) = pybytes(x)
147+
Py(x::Tuple) = pytuple_fromiter(x)
148+
Py(x::Pair) = pytuple_fromiter(x)
149+
Py(x::Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}) = pyint(x)
150+
Py(x::Rational{<:Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}}) = pyfraction(x)
151+
Py(x::Union{Float16,Float32,Float64}) = pyfloat(x)
152+
Py(x::Complex{<:Union{Float16,Float32,Float64}}) = pycomplex(x)
153+
Py(x::AbstractRange{<:Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}}) = pyrange_fromrange(x)
154+
Py(x::Date) = pydate(x)
155+
Py(x::Time) = pytime(x)
156+
Py(x::DateTime) = pydatetime(x)
157+
Py(x) = ispy(x) ? throw(MethodError(Py, (x,))) : pyjl(x)
193158

194159
Base.string(x::Py) = pyisnull(x) ? "<py NULL>" : pystr(String, x)
195160
Base.print(io::IO, x::Py) = print(io, string(x))

src/abstract/collection.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ for N in 0:16
119119
:($z = @pyconvert_and_del($T, pytuple_getitem(xs, $(i-1))))
120120
for (i, T, z) in zip(1:N, Ts, zs)
121121
)...)
122-
pydel!(xs)
123122
return pyconvert_return(($(zs...),))
124123
end
125124
# Tuple with N elements plus Vararg
@@ -136,7 +135,6 @@ for N in 0:16
136135
v = @pyconvert_and_del(V, pytuple_getitem(xs, i-1))
137136
push!(vs, v)
138137
end
139-
pydel!(xs)
140138
return pyconvert_return(($(zs...), vs...))
141139
end
142140
end

src/abstract/object.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,34 +240,39 @@ export pycall
240240
Equivalent to `x == y` in Python. The second form converts to `Bool`.
241241
"""
242242
pyeq(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_EQ)))
243+
243244
"""
244245
pyne(x, y)
245246
pyne(Bool, x, y)
246247
247248
Equivalent to `x != y` in Python. The second form converts to `Bool`.
248249
"""
249250
pyne(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_NE)))
251+
250252
"""
251253
pyle(x, y)
252254
pyle(Bool, x, y)
253255
254256
Equivalent to `x <= y` in Python. The second form converts to `Bool`.
255257
"""
256258
pyle(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_LE)))
259+
257260
"""
258261
pylt(x, y)
259262
pylt(Bool, x, y)
260263
261264
Equivalent to `x < y` in Python. The second form converts to `Bool`.
262265
"""
263266
pylt(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_LT)))
267+
264268
"""
265269
pyge(x, y)
266270
pyge(Bool, x, y)
267271
268272
Equivalent to `x >= y` in Python. The second form converts to `Bool`.
269273
"""
270274
pyge(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_GE)))
275+
271276
"""
272277
pygt(x, y)
273278
pygt(Bool, x, y)
@@ -283,7 +288,7 @@ pyge(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(getpt
283288
pygt(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_GT)) == 1
284289
export pyeq, pyne, pyle, pylt, pyge, pygt
285290

286-
pyconvert_rule_object(::Type{Py}, x) = pyconvert_return(Py(x))
291+
pyconvert_rule_object(::Type{Py}, x::Py) = pyconvert_return(x)
287292

288293
"""
289294
pycontains(x, v)

src/concrete/bool.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
Convert `x` to a Python `bool`.
55
"""
6-
pybool(x::Bool=false) = Py(x ? pybuiltins.True : pybuiltins.False)
6+
pybool(x::Bool=false) = pynew(x ? pybuiltins.True : pybuiltins.False)
77
pybool(x::Number) = pybool(!iszero(x))
88
pybool(x) = pybuiltins.bool(x)
99
export pybool
@@ -23,7 +23,6 @@ pybool_asbool(x) =
2323

2424
function pyconvert_rule_bool(::Type{T}, x::Py) where {T<:Number}
2525
val = pybool_asbool(x)
26-
pydel!(x)
2726
if T in (Bool, Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128, BigInt)
2827
pyconvert_return(T(val))
2928
else

src/concrete/code.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ function _pyeval_args(globals, locals)
99
ArgumentError("globals must be a module or a Python dict")
1010
end
1111
if locals === nothing
12-
locals_ = Py(globals_)
12+
locals_ = pynew(Py(globals_))
1313
elseif ispy(locals)
14-
locals_ = Py(locals)
14+
locals_ = pynew(Py(locals))
1515
else
1616
locals_ = pydict(locals)
1717
end

src/concrete/float.jl

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ 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)
2019
if T in (Float16, Float32, Float64, BigFloat)
2120
pyconvert_return(T(val))
2221
else
@@ -28,7 +27,6 @@ end
2827
# so we allow converting it to Nothing or Missing
2928
function pyconvert_rule_float(::Type{Nothing}, x::Py)
3029
val = pyfloat_asdouble(x)
31-
pydel!(x)
3230
if isnan(val)
3331
pyconvert_return(nothing)
3432
else
@@ -38,7 +36,6 @@ end
3836

3937
function pyconvert_rule_float(::Type{Missing}, x::Py)
4038
val = pyfloat_asdouble(x)
41-
pydel!(x)
4239
if isnan(val)
4340
pyconvert_return(missing)
4441
else

src/concrete/int.jl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ 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)
4342
return pyconvert_tryconvert(T, v)
4443
elseif errmatches(pybuiltins.OverflowError)
4544
# overflows Clonglong or Culonglong
@@ -60,20 +59,17 @@ pyconvert_rule_int(::Type{T}, x::Py) where {T<:Number} = begin
6059
typemin(typeof(v)) typemin(T) &&
6160
typemax(T) typemax(typeof(v))
6261
# definitely overflows S, give up now
63-
pydel!(x)
6462
return pyconvert_unconverted()
6563
else
6664
# try converting -> int -> str -> BigInt -> T
6765
x_int = pyint(x)
68-
pydel!(x)
6966
x_str = pystr(String, x_int)
7067
pydel!(x_int)
7168
v = parse(BigInt, x_str)
7269
return pyconvert_tryconvert(T, v)
7370
end
7471
else
7572
# other error
76-
pydel!(x)
7773
pythrow()
7874
end
7975
end

0 commit comments

Comments
 (0)