Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancing jlwrap #733

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 49 additions & 43 deletions src/PyCall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,11 @@ current_python() = _current_python[]
const _current_python = Ref(pyprogramname)

#########################################################################

# Mirror of C PyObject struct (for non-debugging Python builds).
# We won't actually access these fields directly; we'll use the Python
# C API for everything. However, we need to define a unique Ptr type
# for PyObject*, and we might as well define the actual struct layout
# while we're at it.
struct PyObject_struct
ob_refcnt::Int
ob_type::Ptr{Cvoid}
end

const PyPtr = Ptr{PyObject_struct} # type for PythonObject* in ccall

const PyPtr_NULL = PyPtr(C_NULL)
# interface to libpython
include("libpython/types.jl")
include("libpython/functions.jl")
include("libpython/globals.jl")
include("libpython/extensions.jl")

#########################################################################
# Wrapper around Python's C PyObject* type, with hooks to Python reference
Expand Down Expand Up @@ -154,17 +145,25 @@ wrapping/converted from `x` is created.
"""
pyreturn(x) = PyPtr(pyincref(PyObject(x)))

macro pyreturn(x)
:(return pyreturn($(esc(x))))
end

function Base.copy!(dest::PyObject, src::PyObject)
pydecref(dest)
setfield!(dest, :o, PyPtr(pyincref(src)))
return dest
end

pyisinstance(o::PyObject, t::PyObject) =
!ispynull(t) && ccall((@pysym :PyObject_IsInstance), Cint, (PyPtr,PyPtr), o, t) == 1
pyissubclass(o, t) = GC.@preserve o t pyissubclass(PyPtr(o), PyPtr(t))

pyissubclass(o::PyPtr, t::PyPtr) =
o != C_NULL && t != C_NULL && CPyObject_IsSubclass(o, t)==1

pyisinstance(o, t) = GC.@preserve o t pyisinstance(PyPtr(o), PyPtr(t))

pyisinstance(o::PyObject, t::Union{Ptr{Cvoid},PyPtr}) =
t != C_NULL && ccall((@pysym :PyObject_IsInstance), Cint, (PyPtr,PyPtr), o, t) == 1
pyisinstance(o::PyPtr, t::PyPtr) =
o != C_NULL && t != C_NULL && CPyObject_IsInstance(o, t)==1

pyquery(q::Ptr{Cvoid}, o::PyObject) =
ccall(q, Cint, (PyPtr,), o) == 1
Expand All @@ -187,14 +186,19 @@ pytypeof(o::PyObject) = ispynull(o) ? throw(ArgumentError("NULL PyObjects have n
#########################################################################

const TypeTuple = Union{Type,NTuple{N, Type}} where {N}
include("conversions.jl")
include("pybuffer.jl")
include("pyarray.jl")
include("conversions.jl")
include("pytype.jl")
include("pydict.jl")
include("pyvector.jl")
# include("pytype.jl")
include("jlwrap.jl")
include("pyiterator.jl")
include("pyclass.jl")
include("callback.jl")
include("io.jl")
include("numpy.jl")
include("pydates.jl")

#########################################################################

Expand All @@ -212,18 +216,22 @@ PyObject(o::PyPtr, keep::Any) = pyembed(PyObject(o), keep)

Return a string representation of `o` corresponding to `str(o)` in Python.
"""
pystr(o::PyObject) = convert(AbstractString,
PyObject(@pycheckn ccall((@pysym :PyObject_Str), PyPtr,
(PyPtr,), o)))
function pystr(o::PyObject)
r = CPyObject_Str(String, o)
r===nothing && _handle_error("pystr")
r
end

"""
pyrepr(o::PyObject)

Return a string representation of `o` corresponding to `repr(o)` in Python.
"""
pyrepr(o::PyObject) = convert(AbstractString,
PyObject(@pycheckn ccall((@pysym :PyObject_Repr), PyPtr,
(PyPtr,), o)))
function pyrepr(o::PyObject)
r = CPyObject_Repr(String, o)
r===nothing && _handle_error("pyrepr")
r
end

"""
pystring(o::PyObject)
Expand All @@ -236,16 +244,13 @@ function pystring(o::PyObject)
if ispynull(o)
return "NULL"
else
s = ccall((@pysym :PyObject_Repr), PyPtr, (PyPtr,), o)
if (s == C_NULL)
pyerr_clear()
s = ccall((@pysym :PyObject_Str), PyPtr, (PyPtr,), o)
if (s == C_NULL)
pyerr_clear()
return string(PyPtr(o))
end
end
return convert(AbstractString, PyObject(s))
r = CPyObject_Repr(String, o)
r===nothing || (return r)
CPyErr_Clear()
r = CPyObject_Str(String, o)
r===nothing || (return r)
CPyErr_Clear()
return convert(String, string(PyPtr(o)))
end
end

Expand Down Expand Up @@ -275,7 +280,7 @@ function hash(o::PyObject)
elseif is_pyjlwrap(o)
# call native Julia hash directly on wrapped Julia objects,
# since on 64-bit Windows the Python 2.x hash is only 32 bits
hashsalt(unsafe_pyjlwrap_to_objref(o))
hashsalt(unsafe_pyjlwrap_load_value(o))
else
h = ccall((@pysym :PyObject_Hash), Py_hash_t, (PyPtr,), o)
if h == -1 # error
Expand Down Expand Up @@ -305,7 +310,8 @@ end

getproperty(o::PyObject, s::Symbol) = convert(PyAny, getproperty(o, String(s)))

propertynames(o::PyObject) = ispynull(o) ? Symbol[] : map(x->Symbol(first(x)), pycall(inspect."getmembers", PyObject, o))
propertynames(o::PyObject) =
ispynull(o) ? Symbol[] : map(Symbol∘pystr∘first∘PyIterator{PyObject}, PyIterator{PyObject}(pycall(inspect."getmembers", PyObject, o)))

# avoiding method ambiguity
setproperty!(o::PyObject, s::Symbol, v) = _setproperty!(o,s,v)
Expand Down Expand Up @@ -917,11 +923,11 @@ include("pyinit.jl")
# Here, we precompile functions that are passed to cfunction by __init__,
# for the reasons described in JuliaLang/julia#12256.

precompile(pyjlwrap_call, (PyPtr,PyPtr,PyPtr))
precompile(pyjlwrap_dealloc, (PyPtr,))
precompile(pyjlwrap_repr, (PyPtr,))
precompile(pyjlwrap_hash, (PyPtr,))
precompile(pyjlwrap_hash32, (PyPtr,))
precompile(_pyjlwrap_call, (PyPtr,PyPtr,PyPtr))
precompile(_pyjlwrap_dealloc, (PyPtr,))
precompile(_pyjlwrap_repr, (PyPtr,))
precompile(_pyjlwrap_hash, (PyPtr,))
precompile(_pyjlwrap_hash32, (PyPtr,))

# TODO: precompilation of the io.jl functions

Expand Down
109 changes: 109 additions & 0 deletions src/builtins.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
pystr(o::PyObject)

Return a string representation of `o` corresponding to `str(o)` in Python.
"""
pystr(args...; kwargs...) =
pystr(AbstractString, args...; kwargs...)
pystr(T::Type{<:AbstractString}, args...; kwargs...) =
convert(T, pystr(PyObject, args...; kwargs...))
pystr(::Type{PyObject}, o) =
PyObject(@pycheckn ccall(@pysym(:PyObject_Str), PyPtr, (PyPtr,), PyObject(o)))
pystr(::Type{PyObject}, o::AbstractString="") =
convertpystr(o)

"""
pyrepr(o::PyObject)

Return a string representation of `o` corresponding to `repr(o)` in Python.
"""
pyrepr(o) =
pystr(AbstractString, o)
pyrepr(::Type{T}, o) where {T<:AbstractString} =
convert(T, pyrepr(PyObject, o))
pyrepr(::Type{PyObject}, o) =
PyObject(@pycheckn ccall(@pysym(:PyObject_Repr), PyPtr, (PyPtr,), PyObject(o)))

"""
pyisinstance(o::PyObject, t::PyObject)

True if `o` is an instance of `t`, corresponding to `isinstance(o,t)` in Python.
"""
pyisinstance(o, t) =
pyisinstance(Bool, o, t)
pyisinstance(::Type{Bool}, o, t) =
pyisinstance(Bool, PyObject(o), PyObject(t))
pyisinstance(::Type{Bool}, o::PyObject, t::PyObject) =
!ispynull(t) && ccall((@pysym :PyObject_IsInstance), Cint, (PyPtr,PyPtr), o, t) == 1
pyisinstance(::Type{Bool}, o::PyObject, t::Union{Ptr{Cvoid},PyPtr}) =
t != C_NULL && ccall((@pysym :PyObject_IsInstance), Cint, (PyPtr,PyPtr), o, t) == 1
pyisinstance(::Type{PyObject}, o, t) =
convertpybool(pyisinstance(Bool, o, t))

"""
pyistrue(o::PyObject)

True if `o` is considered to be true, corresponding to `not not o` in Python.
"""
pyistrue(o::PyObject) =
pyistrue(Bool, o)
pyistrue(::Type{Bool}, o::PyObject) =
(@pycheckz ccall(@pysym(:PyObject_IsTrue), Cint, (PyPtr,), o)) == 1
pyistrue(::Type{PyObject}, o) =
convertpybool(pyistrue(Bool, o))

"""
pynot(o::PyObject)

True if `o` is not considered to be true, corresponding to `not o` in Python.
"""
pynot(o::PyObject) =
pynot(Bool, o)
pynot(::Type{Bool}, o::PyObject) =
(@pycheckz ccall(@pysym(:PyObject_Not), Cint, (PyPtr,), o)) == 1
pynot(::Type{PyObject}, o) =
convertpybool(pynot(Bool, o))

"""
pyint(o::PyObject)
"""
pyint(args...; kwargs...) =
pyint(PyObject, args...; kwargs...)
pyint(T::Type, args...; kwargs...) =
pycall(pybuiltin("int"), T, args...; kwargs...)
pyint(::Type{PyObject}, o::Integer=0) =
convertpyint(o)

"""
pybool(o::PyObject)
"""
pybool(args...; kwargs...) =
pybool(PyObject, args...; kwargs...)
pybool(T::Type, args...; kwargs...) =
pycall(pybuiltin("bool"), T, args...; kwargs...)
pybool(::Type{PyObject}, o::Bool=false) =
convertpybool(o)

"""
pystring(o::PyObject)

Return a string representation of `o`. Normally, this corresponds to `repr(o)`
in Python, but unlike `repr` it should never fail (falling back to `str` or to
printing the raw pointer if necessary).
"""
function pystring(o::PyObject)
if ispynull(o)
return "NULL"
else
s = ccall((@pysym :PyObject_Repr), PyPtr, (PyPtr,), o)
if (s == C_NULL)
pyerr_clear()
s = ccall((@pysym :PyObject_Str), PyPtr, (PyPtr,), o)
if (s == C_NULL)
pyerr_clear()
return string(PyPtr(o))
end
end
return convert(AbstractString, PyObject(s))
end
end
31 changes: 0 additions & 31 deletions src/callback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,6 @@
julia_args(f, args) = convert(PyAny, args)
julia_kwarg(f, kw, arg) = convert(PyAny, arg)

function _pyjlwrap_call(f, args_::PyPtr, kw_::PyPtr)
args = PyObject(args_) # don't need pyincref because of finally clause below
try
jlargs = julia_args(f, args)

# we need to use invokelatest to get execution in newest world
if kw_ == C_NULL
ret = Base.invokelatest(f, jlargs...)
else
kw = PyDict{Symbol,PyObject}(pyincref(kw_))
kwargs = [ (k,julia_kwarg(f,k,v)) for (k,v) in kw ]

# 0.6 `invokelatest` doesn't support kwargs, instead
# use a closure over kwargs. see:
# https://github.com/JuliaLang/julia/pull/22646
f_kw_closure() = f(jlargs...; kwargs...)
ret = Core._apply_latest(f_kw_closure)
end

return pyreturn(ret)
catch e
@pyraise e
finally
setfield!(args, :o, PyPtr_NULL) # don't decref
end
return PyPtr_NULL
end

pyjlwrap_call(self_::PyPtr, args_::PyPtr, kw_::PyPtr) =
_pyjlwrap_call(unsafe_pyjlwrap_to_objref(self_), args_, kw_)

################################################################
# allow the user to convert a Julia function into a Python
# function with specified argument types, both to give more control
Expand Down
Loading