Skip to content

Commit 49029e0

Browse files
tkfstevengj
authored andcommitted
Don't steal reference from existing Python object (#553)
* Don't steal reference from existing Python object closes #551 * Refactoring: add pyreturn function * Fix pystealref! in pyjlwrap_getattr * Fix pystealref! in pyjlwrap_iternext
1 parent 3d45ec2 commit 49029e0

File tree

5 files changed

+68
-6
lines changed

5 files changed

+68
-6
lines changed

src/PyCall.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,16 @@ function pystealref!(o::PyObject)
133133
return optr
134134
end
135135

136+
"""
137+
pyreturn(x) :: PyPtr
138+
139+
Prepare `PyPtr` from `x` for passing it to Python. If `x` is already
140+
a `PyObject`, the refcount is incremented. Otherwise a `PyObject`
141+
wrapping/converted from `x` is created.
142+
"""
143+
pyreturn(x::Any) = pystealref!(PyObject(x))
144+
pyreturn(x::PyObject) = pyincref_(x.o)
145+
136146
function Base.copy!(dest::PyObject, src::PyObject)
137147
pydecref(dest)
138148
dest.o = src.o

src/callback.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function _pyjlwrap_call(f, args_::PyPtr, kw_::PyPtr)
2525

2626
# we need to use invokelatest to get execution in newest world
2727
if kw_ == C_NULL
28-
ret = PyObject(Base.invokelatest(f, jlargs...))
28+
ret = Base.invokelatest(f, jlargs...)
2929
else
3030
kw = PyDict{Symbol,PyObject}(pyincref(kw_))
3131
kwargs = [ (k,julia_kwarg(f,k,v)) for (k,v) in kw ]
@@ -34,10 +34,10 @@ function _pyjlwrap_call(f, args_::PyPtr, kw_::PyPtr)
3434
# use a closure over kwargs. see:
3535
# https://github.com/JuliaLang/julia/pull/22646
3636
f_kw_closure() = f(jlargs...; kwargs...)
37-
ret = PyObject(Core._apply_latest(f_kw_closure))
37+
ret = Core._apply_latest(f_kw_closure)
3838
end
3939

40-
return pystealref!(ret)
40+
return pyreturn(ret)
4141
catch e
4242
pyraise(e)
4343
finally

src/pyiterator.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const jlWrapIteratorType = PyTypeObject()
6565
if !done(iter, state)
6666
item, state′ = next(iter, state)
6767
stateref[] = state′ # stores new state in the iterator object
68-
return pystealref!(PyObject(item))
68+
return pyreturn(item)
6969
end
7070
catch e
7171
pyraise(e)
@@ -80,7 +80,7 @@ else
8080
if iter_result !== nothing
8181
item, state = iter_result
8282
iter_result_ref[] = iterate(iter, state)
83-
return pystealref!(PyObject(item))
83+
return pyreturn(item)
8484
end
8585
catch e
8686
pyraise(e)

src/pytype.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ function pyjlwrap_getattr(self_::PyPtr, attr__::PyPtr)
377377
else
378378
fidx = Base.fieldindex(typeof(f), Symbol(attr), false)
379379
if fidx != 0
380-
return pystealref!(PyObject(getfield(f, fidx)))
380+
return pyreturn(getfield(f, fidx))
381381
else
382382
return ccall(@pysym(:PyObject_GenericGetAttr), PyPtr, (PyPtr,PyPtr), self_, attr__)
383383
end

test/runtests.jl

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,4 +573,56 @@ end
573573
@test (@pywith IgnoreError(true) error(); true)
574574
end
575575

576+
@testset "callback" begin
577+
# Returning existing PyObject in Julia should not invalidate it.
578+
# https://github.com/JuliaPy/PyCall.jl/pull/552
579+
anonymous = Module()
580+
Base.eval(
581+
anonymous, quote
582+
using PyCall
583+
obj = pyimport("sys") # get some PyObject
584+
end)
585+
py"""
586+
ns = {}
587+
def set(name):
588+
ns[name] = $include_string($anonymous, name)
589+
"""
590+
py"set"("obj")
591+
@test anonymous.obj != PyNULL()
592+
593+
# Test above for pyjlwrap_getattr too:
594+
anonymous = Module()
595+
Base.eval(
596+
anonymous, quote
597+
using PyCall
598+
struct S
599+
x
600+
end
601+
obj = S(pyimport("sys"))
602+
end)
603+
py"""
604+
ns = {}
605+
def set(name):
606+
ns[name] = $include_string($anonymous, name).x
607+
"""
608+
py"set"("obj")
609+
@test anonymous.obj.x != PyNULL()
610+
611+
# Test above for pyjlwrap_iternext too:
612+
anonymous = Module()
613+
Base.eval(
614+
anonymous, quote
615+
using PyCall
616+
sys = pyimport("sys")
617+
obj = (sys for _ in 1:1)
618+
end)
619+
py"""
620+
ns = {}
621+
def set(name):
622+
ns[name] = list(iter($include_string($anonymous, name)))
623+
"""
624+
py"set"("obj")
625+
@test anonymous.sys != PyNULL()
626+
end
627+
576628
include("test_pyfncall.jl")

0 commit comments

Comments
 (0)