diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 454888d7..a037a01b 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -6,9 +6,9 @@ | ------- | ------ | ------------------- | ----------- | | Python | 1.0x | 280 | ? | | PythonCall | 2.4x | 680 | 5008 | -| PythonCall + `pydel!` | 1.1x | 300 | 1008 | +| PythonCall + `unsafe_pydel!` | 1.1x | 300 | 1008 | | PythonCall `@py` | 1.4x | 420 | 1002 | -| PythonCall `@py` + `@pydel!` | 1.1x | 300 | 2 | +| PythonCall `@py` + `@unsafe_pydel!` | 1.1x | 300 | 2 | | PyCall | 5.4x | 1620 | 10987 | | PyCall (readable but wrong) | 5.9x | 1784 | 11456 | @@ -42,7 +42,7 @@ test (generic function with 1 method) julia> @benchmark test() ``` -PythonCall + `pydel!` code: +PythonCall + `unsafe_pydel!` code: ```julia-repl julia> using PythonCall, BenchmarkTools @@ -54,10 +54,10 @@ julia> function test() r = random() v = i + r x[k] = v - pydel!(k) - pydel!(r) - pydel!(v) - pydel!(i) + unsafe_pydel!(k) + unsafe_pydel!(r) + unsafe_pydel!(v) + unsafe_pydel!(i) end return x end @@ -75,8 +75,8 @@ julia> test() = @py begin x = {} for i in range(1000) x[str(i)] = i + random() - # Uncomment for pydel! version: - # @jl PythonCall.pydel!(i) + # Uncomment for unsafe_pydel! version: + # @jl PythonCall.unsafe_pydel!(i) end x end diff --git a/docs/src/faq.md b/docs/src/faq.md index 981aa1ed..1cbcb207 100644 --- a/docs/src/faq.md +++ b/docs/src/faq.md @@ -6,8 +6,8 @@ No. Some rules if you are writing multithreaded code: - Only call Python functions from the first thread. -- You probably also need to call `PythonCall.GC.disable()` on the main thread before any - threaded block of code. Remember to call `PythonCall.GC.enable()` again afterwards. +- You probably also need to call `on=PythonCall.GC.enable(false)` on the main thread before any + threaded block of code. Remember to call `PythonCall.GC.enable(on)` again afterwards. (This is because Julia finalizers can be called from any thread.) - Julia intentionally causes segmentation faults as part of the GC safepoint mechanism. If unhandled, these segfaults will result in termination of the process. To enable signal handling, diff --git a/docs/src/pythoncall-reference.md b/docs/src/pythoncall-reference.md index 75065f50..8de963da 100644 --- a/docs/src/pythoncall-reference.md +++ b/docs/src/pythoncall-reference.md @@ -229,16 +229,17 @@ PythonCall.python_library_path PythonCall.python_library_handle ``` -## Low-level API +## Mutable API -The functions here are not exported. They are mostly unsafe in the sense that you can -crash Julia by using them incorrectly. +```@docs +pynew +pyisnew +pycopy! +``` + +## Unsafe API ```@docs -PythonCall.pynew -PythonCall.pyisnull -PythonCall.pycopy! -PythonCall.getptr -PythonCall.pydel! +PythonCall.unsafe_pydel! PythonCall.unsafe_pynext ``` diff --git a/docs/src/releasenotes.md b/docs/src/releasenotes.md index 6d02da11..0db71372 100644 --- a/docs/src/releasenotes.md +++ b/docs/src/releasenotes.md @@ -1,5 +1,8 @@ # Release Notes +## Unreleased (v1) +* `PythonCall.GC` is now more like `Base.GC`: `enable(true)` replaces `enable()`, `enable(false)` replaces `disable()`, and `gc()` is added. + ## 0.9.17 (2024-03-16) * Bug fixes. diff --git a/pysrc/juliacall/__init__.py b/pysrc/juliacall/__init__.py index d9e7a594..4c8be096 100644 --- a/pysrc/juliacall/__init__.py +++ b/pysrc/juliacall/__init__.py @@ -28,7 +28,7 @@ def interactive(enable=True): else: PythonCall.Compat._unset_python_input_hook() -class JuliaError(Exception): +class JlError(Exception): "An error arising in Julia code." def __init__(self, exception, backtrace=None): super().__init__(exception, backtrace) @@ -36,9 +36,9 @@ def __str__(self): e = self.exception b = self.backtrace if b is None: - return Base.sprint(Base.showerror, e) + return str(Base.sprint(Base.showerror, e)) else: - return Base.sprint(Base.showerror, e, b) + return str(Base.sprint(Base.showerror, e, b)) @property def exception(self): return self.args[0] diff --git a/src/C/extras.jl b/src/C/extras.jl index e84ebed7..060e860c 100644 --- a/src/C/extras.jl +++ b/src/C/extras.jl @@ -1,22 +1,28 @@ -Py_Type(x::PyPtr) = PyPtr(UnsafePtr(x).type[!]) +cptr(x) = Base.cconvert(PyPtr, x) +uptr(x) = Base.unsafe_convert(PyPtr, x) +ptr(x) = uptr(cptr(x)) # TODO: really all uses of this should use GC.@preserve on cptr(x) -PyObject_Type(x::PyPtr) = (t=Py_Type(x); Py_IncRef(t); t) +Py_Type(x) = PyPtr(UnsafePtr(ptr(x)).type[!]) -Py_TypeCheck(o::PyPtr, t::PyPtr) = PyType_IsSubtype(Py_Type(o), t) -Py_TypeCheckFast(o::PyPtr, f::Integer) = PyType_IsSubtypeFast(Py_Type(o), f) +PyObject_Type(x) = (t=Py_Type(x); Py_IncRef(t); t) -PyType_IsSubtypeFast(t::PyPtr, f::Integer) = Cint(!iszero(UnsafePtr{PyTypeObject}(t).flags[] & f)) +Py_TypeCheck(o, t) = PyType_IsSubtype(Py_Type(o), t) +Py_TypeCheckFast(o, f::Integer) = PyType_IsSubtypeFast(Py_Type(o), f) -PyMemoryView_GET_BUFFER(m::PyPtr) = Ptr{Py_buffer}(UnsafePtr{PyMemoryViewObject}(m).view) +PyTuple_Check(o) = Py_TypeCheckFast(o, Py_TPFLAGS_TUPLE_SUBCLASS) -PyType_CheckBuffer(t::PyPtr) = begin - p = UnsafePtr{PyTypeObject}(t).as_buffer[] +PyType_IsSubtypeFast(t, f::Integer) = !iszero(UnsafePtr{PyTypeObject}(ptr(t)).flags[] & f) + +PyMemoryView_GET_BUFFER(m) = Ptr{Py_buffer}(UnsafePtr{PyMemoryViewObject}(ptr(m)).view) + +function PyType_CheckBuffer(t) + p = UnsafePtr{PyTypeObject}(ptr(t)).as_buffer[] return p != C_NULL && p.get[!] != C_NULL end -PyObject_CheckBuffer(o::PyPtr) = PyType_CheckBuffer(Py_Type(o)) +PyObject_CheckBuffer(o) = PyType_CheckBuffer(Py_Type(o)) -PyObject_GetBuffer(o::PyPtr, b, flags) = begin +function PyObject_GetBuffer(o, b, flags) p = UnsafePtr{PyTypeObject}(Py_Type(o)).as_buffer[] if p == C_NULL || p.get[!] == C_NULL PyErr_SetString( @@ -28,8 +34,8 @@ PyObject_GetBuffer(o::PyPtr, b, flags) = begin return ccall(p.get[!], Cint, (PyPtr, Ptr{Py_buffer}, Cint), o, b, flags) end -PyBuffer_Release(_b) = begin - b = UnsafePtr(Base.unsafe_convert(Ptr{Py_buffer}, _b)) +function PyBuffer_Release(_b) + b = UnsafePtr(Base.unsafe_convert(Ptr{Py_buffer}, ptr(_b))) o = b.obj[] o == C_NULL && return p = UnsafePtr{PyTypeObject}(Py_Type(o)).as_buffer[] @@ -60,8 +66,8 @@ function PyOS_RunInputHook() end end -function PySimpleObject_GetValue(::Type{T}, o::PyPtr) where {T} - UnsafePtr{PySimpleObject{T}}(o).value[!] +function PySimpleObject_GetValue(::Type{T}, o) where {T} + UnsafePtr{PySimpleObject{T}}(ptr(o)).value[!] end # FAST REFCOUNTING diff --git a/src/C/pointers.jl b/src/C/pointers.jl index 4d63c578..2eceddc4 100644 --- a/src/C/pointers.jl +++ b/src/C/pointers.jl @@ -51,7 +51,7 @@ const CAPI_FUNC_SIGS = Dict{Symbol, Pair{Tuple, Type}}( :PyObject_GenericGetAttr => (PyPtr, PyPtr) => PyPtr, :PyObject_SetAttrString => (PyPtr, Ptr{Cchar}, PyPtr) => Cint, :PyObject_SetAttr => (PyPtr, PyPtr, PyPtr) => Cint, - :PyObject_GenericSetAttr => (PyPtr, PyPtr, PyPtr) => PyPtr, + :PyObject_GenericSetAttr => (PyPtr, PyPtr, PyPtr) => Cint, :PyObject_RichCompare => (PyPtr, PyPtr, Cint) => PyPtr, :PyObject_RichCompareBool => (PyPtr, PyPtr, Cint) => Cint, :PyObject_Repr => (PyPtr,) => PyPtr, @@ -159,6 +159,7 @@ const CAPI_FUNC_SIGS = Dict{Symbol, Pair{Tuple, Type}}( :PyDict_SetItem => (PyPtr, PyPtr, PyPtr) => Cint, :PyDict_SetItemString => (PyPtr, Ptr{Cchar}, PyPtr) => Cint, :PyDict_DelItemString => (PyPtr, Ptr{Cchar}) => Cint, + :PyDict_Size => (PyPtr,) => Py_ssize_t, # SET :PySet_New => (PyPtr,) => PyPtr, :PyFrozenSet_New => (PyPtr,) => PyPtr, diff --git a/src/Compat/Compat.jl b/src/Compat/Compat.jl index dcb1398d..c2d0631c 100644 --- a/src/Compat/Compat.jl +++ b/src/Compat/Compat.jl @@ -6,7 +6,7 @@ Misc bits and bobs for compatibility. module Compat using ..PythonCall: PythonCall # needed for docstring cross-refs using ..Core - using ..Core: C, Utils, pynew, incref, getptr, pycopy!, pymodulehooks, pyisnull, pybytes_asvector, pysysmodule, pyosmodule, pystr_fromUTF8 + using ..Core: C, Utils, pynew, incref, getptr, pycopy!, pymodulehooks, pyisnew, pybytes_asvector, pysysmodule, pyosmodule, pystr_fromUTF8 using ..Convert: pyconvert, @pyconvert using ..Wrap: PyArray, PyPandasDataFrame using Serialization: Serialization, AbstractSerializer, serialize, deserialize diff --git a/src/Compat/serialization.jl b/src/Compat/serialization.jl index 5f600673..9d832110 100644 --- a/src/Compat/serialization.jl +++ b/src/Compat/serialization.jl @@ -3,7 +3,7 @@ # We use pickle to serialise Python objects to bytes. function serialize_py(s, x::Py) - if pyisnull(x) + if pyisnew(x) serialize(s, nothing) else b = pyimport("pickle").dumps(x) diff --git a/src/Convert/Convert.jl b/src/Convert/Convert.jl index 961e84f0..d011a4e2 100644 --- a/src/Convert/Convert.jl +++ b/src/Convert/Convert.jl @@ -6,7 +6,7 @@ Implements `pyconvert`. module Convert using ..Core -using ..Core: C, Utils, @autopy, getptr, incref, pynew, PyNULL, pyisnull, pydel!, pyisint, iserrset_ambig, pyisnone, pyisTrue, pyisFalse, pyfloat_asdouble, pycomplex_ascomplex, pyisstr, pystr_asstring, pyisbytes, pybytes_asvector, pybytes_asUTF8string, pyisfloat, pyisrange, pytuple_getitem, unsafe_pynext, pyistuple, pydatetimetype, pytime_isaware, pydatetime_isaware, _base_pydatetime, _base_datetime, errmatches, errclear, errset, pyiscomplex, pythrow, pybool_asbool +using ..Core: C, Utils, @autopy, getptr, incref, pynew, PyNULL, pyisnew, unsafe_pydel!, pyisint, iserrset_ambig, pyisnone, pyisTrue, pyisFalse, pyfloat_asdouble, pycomplex_ascomplex, pyisstr, pystr_asstring, pyisbytes, pybytes_asvector, pybytes_asUTF8string, pyisfloat, pyisrange, pytuple_getitem, unsafe_pynext, pyistuple, pydatetimetype, pytime_isaware, pydatetime_isaware, _base_pydatetime, _base_datetime, errmatches, errclear, errset, pyiscomplex, pythrow, pybool_asbool using Dates: Date, Time, DateTime, Millisecond import ..Core: pyconvert diff --git a/src/Convert/ctypes.jl b/src/Convert/ctypes.jl index b9b47c93..969b4b79 100644 --- a/src/Convert/ctypes.jl +++ b/src/Convert/ctypes.jl @@ -1,7 +1,7 @@ struct pyconvert_rule_ctypessimplevalue{R,S} <: Function end function (::pyconvert_rule_ctypessimplevalue{R,SAFE})(::Type{T}, x::Py) where {R,SAFE,T} - ptr = Base.GC.@preserve x C.PySimpleObject_GetValue(Ptr{R}, getptr(x)) + ptr = Base.GC.@preserve x C.PySimpleObject_GetValue(Ptr{R}, x) ans = unsafe_load(ptr) if SAFE pyconvert_return(convert(T, ans)) diff --git a/src/Convert/numpy.jl b/src/Convert/numpy.jl index 30eed08a..8ed8f898 100644 --- a/src/Convert/numpy.jl +++ b/src/Convert/numpy.jl @@ -1,7 +1,7 @@ struct pyconvert_rule_numpysimplevalue{R,S} <: Function end function (::pyconvert_rule_numpysimplevalue{R,SAFE})(::Type{T}, x::Py) where {R,SAFE,T} - ans = Base.GC.@preserve x C.PySimpleObject_GetValue(R, getptr(x)) + ans = Base.GC.@preserve x C.PySimpleObject_GetValue(R, x) if SAFE pyconvert_return(convert(T, ans)) else diff --git a/src/Convert/pyconvert.jl b/src/Convert/pyconvert.jl index a7293051..a6e098e0 100644 --- a/src/Convert/pyconvert.jl +++ b/src/Convert/pyconvert.jl @@ -162,7 +162,7 @@ function _pyconvert_get_rules(pytype::Py) xbase = base end end - if !pyisnull(xbase) + if !pyisnew(xbase) push!(basetypes, xtype) xmro = collect(xtype.__mro__) pyisin(xbase, xmro) || pushfirst!(xmro, xbase) @@ -227,7 +227,7 @@ function _pyconvert_get_rules(pytype::Py) end end for (t, x) in reverse(collect(zip(mro, xmro))) - if C.PyType_CheckBuffer(getptr(t)) + if C.PyType_CheckBuffer(t) push!(x, "") break end @@ -323,12 +323,12 @@ function pytryconvert(::Type{T}, x_) where {T} # get rules from the cache # TODO: we should hold weak references and clear the cache if types get deleted - tptr = C.Py_Type(getptr(x)) + tptr = C.Py_Type(x) trules = pyconvert_rules_cache(T) rules = get!(trules, tptr) do t = pynew(incref(tptr)) ans = pyconvert_get_rules(T, t)::Vector{Function} - pydel!(t) + unsafe_pydel!(t) ans end diff --git a/src/Convert/rules.jl b/src/Convert/rules.jl index 8d0dc166..044c4098 100644 --- a/src/Convert/rules.jl +++ b/src/Convert/rules.jl @@ -44,7 +44,7 @@ pyconvert_rule_bytes(::Type{Base.CodeUnits{UInt8,String}}, x::Py) = pyconvert_re pyconvert_rule_int(::Type{T}, x::Py) where {T<:Number} = begin # first try to convert to Clonglong (or Culonglong if unsigned) - v = T <: Unsigned ? C.PyLong_AsUnsignedLongLong(getptr(x)) : C.PyLong_AsLongLong(getptr(x)) + v = T <: Unsigned ? C.PyLong_AsUnsignedLongLong(x) : C.PyLong_AsLongLong(x) if !iserrset_ambig(v) # success return pyconvert_tryconvert(T, v) @@ -72,7 +72,7 @@ pyconvert_rule_int(::Type{T}, x::Py) where {T<:Number} = begin # try converting -> int -> str -> BigInt -> T x_int = pyint(x) x_str = pystr(String, x_int) - pydel!(x_int) + unsafe_pydel!(x_int) v = parse(BigInt, x_str) return pyconvert_tryconvert(T, v) end @@ -169,8 +169,8 @@ end function _pyconvert_rule_iterable(ans::Vector{T0}, it::Py, ::Type{T1}) where {T0,T1} @label again x_ = unsafe_pynext(it) - if pyisnull(x_) - pydel!(it) + if pyisnew(x_) + unsafe_pydel!(it) return pyconvert_return(ans) end x = @pyconvert(T1, x_) @@ -195,8 +195,8 @@ end function _pyconvert_rule_iterable(ans::Set{T0}, it::Py, ::Type{T1}) where {T0,T1} @label again x_ = unsafe_pynext(it) - if pyisnull(x_) - pydel!(it) + if pyisnew(x_) + unsafe_pydel!(it) return pyconvert_return(ans) end x = @pyconvert(T1, x_) @@ -221,8 +221,8 @@ end function _pyconvert_rule_mapping(ans::Dict{K0,V0}, x::Py, it::Py, ::Type{K1}, ::Type{V1}) where {K0,V0,K1,V1} @label again k_ = unsafe_pynext(it) - if pyisnull(k_) - pydel!(it) + if pyisnew(k_) + unsafe_pydel!(it) return pyconvert_return(ans) end v_ = pygetitem(x, k_) @@ -310,25 +310,25 @@ end function pyconvert_rule_iterable(::Type{R}, x::Py, ::Type{Pair{K0,V0}}=Utils._type_lb(R), ::Type{Pair{K1,V1}}=Utils._type_ub(R)) where {R<:Pair,K0,V0,K1,V1} it = pyiter(x) k_ = unsafe_pynext(it) - if pyisnull(k_) - pydel!(it) - pydel!(k_) + if pyisnew(k_) + unsafe_pydel!(it) + unsafe_pydel!(k_) return pyconvert_unconverted() end k = @pyconvert(K1, k_) v_ = unsafe_pynext(it) - if pyisnull(v_) - pydel!(it) - pydel!(v_) + if pyisnew(v_) + unsafe_pydel!(it) + unsafe_pydel!(v_) return pyconvert_unconverted() end v = @pyconvert(V1, v_) z_ = unsafe_pynext(it) - pydel!(it) - if pyisnull(z_) - pydel!(z_) + unsafe_pydel!(it) + if pyisnew(z_) + unsafe_pydel!(z_) else - pydel!(z_) + unsafe_pydel!(z_) return pyconvert_unconverted() end K2 = Utils._promote_type_bounded(K0, typeof(k), K1) @@ -353,7 +353,7 @@ function pyconvert_rule_iterable(::Type{R}, x::Py) where {R<:NamedTuple} pyistuple(x) || return pyconvert_unconverted() names2_ = pygetattr(x, "_fields", pybuiltins.None) names2 = @pyconvert(names === nothing ? Tuple{Vararg{Symbol}} : typeof(names), names2_) - pydel!(names2_) + unsafe_pydel!(names2_) names === nothing || names === names2 || return pyconvert_unconverted() types2 = types === nothing ? NTuple{length(names2),Any} : types vals = @pyconvert(types2, x) @@ -391,7 +391,7 @@ function pyconvert_rule_datetime(::Type{DateTime}, x::Py) days = pyconvert(Int, d.days) seconds = pyconvert(Int, d.seconds) microseconds = pyconvert(Int, d.microseconds) - pydel!(d) + unsafe_pydel!(d) iszero(mod(microseconds, 1000)) || return pyconvert_unconverted() return pyconvert_return(_base_datetime + Millisecond(div(microseconds, 1000) + 1000 * (seconds + 60 * 60 * 24 * days))) end diff --git a/src/Core/Py.jl b/src/Core/Py.jl index 396b39d8..6ef8f0a5 100644 --- a/src/Core/Py.jl +++ b/src/Core/Py.jl @@ -12,19 +12,27 @@ ispy(x) = false export ispy """ - pyisnull(x) + pyisnew(x) True if the Python object `x` is NULL. """ -pyisnull(x) = getptr(x) == C.PyNULL +pyisnew(x) = unsafe_getptr(x) == C.PyNULL +export pyisnew """ getptr(x) -Get the underlying pointer from the Python object `x`. +Get the underlying pointer from the Python object `x`. NULL is interpreted as None. """ getptr(x) = ispy(x) ? getptr(Py(x)::Py) : throw(MethodError(getptr, (x,))) +""" + unsafe_getptr(x) + +Get the underlying pointer from the Python object `x`. Can be NULL. +""" +unsafe_getptr(x) = ispy(x) ? unsafe_getptr(Py(x)::Py) : throw(MethodError(unsafe_getptr, (x,))) + """ Py(x) @@ -39,17 +47,24 @@ return `Py`. """ mutable struct Py ptr :: C.PyPtr - Py(::Val{:new}, ptr::C.PyPtr) = finalizer(py_finalizer, new(ptr)) + global new_Py(ptr::C.PyPtr) = finalizer(py_finalizer, new(ptr)) end export Py -py_finalizer(x::Py) = GC.enqueue(getptr(x)) +py_finalizer(x::Py) = GC.enqueue(unsafe_getptr(x)) ispy(::Py) = true -getptr(x::Py) = getfield(x, :ptr) +unsafe_getptr(x::Py) = getfield(x, :ptr) +getptr(x::Py) = (ptr = unsafe_getptr(x); ifelse(ptr == C.PyNULL, C.POINTERS._Py_NoneStruct, ptr)) pyconvert(::Type{Py}, x::Py) = x -setptr!(x::Py, ptr::C.PyPtr) = (setfield!(x, :ptr, ptr); x) +unsafe_setptr!(x::Py, ptr::C.PyPtr) = (setfield!(x, :ptr, ptr); x) + +struct PyAsPtr + py::Py +end +Base.cconvert(::Type{C.PyPtr}, x::Py) = PyAsPtr(x) +Base.unsafe_convert(::Type{C.PyPtr}, x::PyAsPtr) = getptr(x.py) const PYNULL_CACHE = Py[] @@ -64,18 +79,20 @@ points at, i.e. the new `Py` object owns a reference. Note that NULL Python objects are not safe in the sense that most API functions will probably crash your Julia session if you pass a NULL argument. """ -pynew() = +function pynew() if isempty(PYNULL_CACHE) - Py(Val(:new), C.PyNULL) + new_Py(C.PyNULL) else pop!(PYNULL_CACHE) end +end +export pynew const PyNULL = pynew() -pynew(ptr::C.PyPtr) = setptr!(pynew(), ptr) +pynew(ptr::C.PyPtr) = unsafe_setptr!(pynew(), ptr) -pynew(x::Py) = pynew(incref(getptr(x))) +pynew(x::Py) = pynew(incref(unsafe_getptr(x))) """ pycopy!(dst::Py, src) @@ -89,10 +106,14 @@ the top level then `pycopy!(x, pything())` inside `__init__()`. Assumes `dst` is NULL, otherwise a memory leak will occur. """ -pycopy!(dst::Py, src) = Base.GC.@preserve src setptr!(dst, incref(getptr(src))) +function pycopy!(dst::Py, src) + pyisnew(dst) || error("pyisnew(dst) is false") + Base.GC.@preserve src unsafe_setptr!(dst, incref(unsafe_getptr(src))) +end +export pycopy! """ - pydel!(x::Py) + unsafe_pydel!(x::Py) Delete the Python object `x`. @@ -108,15 +129,16 @@ be a significant source of slow-down in code which uses a lot of Python objects. `pynew()` to pop an item from `PYNULL_CACHE` instead of allocating one, and avoids calling the relatively slow finalizer on `x`. """ -function pydel!(x::Py) - ptr = getptr(x) +function unsafe_pydel!(x::Py) + ptr = unsafe_getptr(x) if ptr != C.PyNULL C.Py_DecRef(ptr) - setptr!(x, C.PyNULL) + unsafe_setptr!(x, C.PyNULL) end push!(PYNULL_CACHE, x) return end +export unsafe_pydel! macro autopy(args...) vs = args[1:end-1] @@ -126,7 +148,7 @@ macro autopy(args...) esc(quote # $([:($t = $ispy($v) ? $v : $Py($v)) for (t, v) in zip(ts, vs)]...) # $ans = $body - # $([:($ispy($v) || $pydel!($t)) for (t, v) in zip(ts, vs)]...) + # $([:($ispy($v) || $unsafe_pydel!($t)) for (t, v) in zip(ts, vs)]...) # $ans $([:($t = $Py($v)) for (t, v) in zip(ts, vs)]...) $body @@ -150,19 +172,19 @@ Py(x::Date) = pydate(x) Py(x::Time) = pytime(x) Py(x::DateTime) = pydatetime(x) -Base.string(x::Py) = pyisnull(x) ? "" : pystr(String, x) +Base.string(x::Py) = pyisnew(x) ? "None" : pystr(String, x) Base.print(io::IO, x::Py) = print(io, string(x)) function Base.show(io::IO, x::Py) if get(io, :typeinfo, Any) == Py - if getptr(x) == C.PyNULL - print(io, "NULL") + if pyisnew(x) + print(io, "None") else print(io, pyrepr(String, x)) end else - if getptr(x) == C.PyNULL - print(io, "") + if pyisnew(x) + print(io, "None") else s = pyrepr(String, x) if startswith(s, "<") && endswith(s, ">") @@ -175,8 +197,8 @@ function Base.show(io::IO, x::Py) end function Base.show(io::IO, ::MIME"text/plain", o::Py) - if pyisnull(o) - str = "NULL" + if pyisnew(o) + str = "None" else str = pyrepr(String, o) end @@ -322,8 +344,8 @@ Base.IteratorSize(::Type{Py}) = Base.SizeUnknown() function Base.iterate(x::Py, it::Py=pyiter(x)) v = unsafe_pynext(it) - if pyisnull(v) - pydel!(it) + if pyisnew(v) + unsafe_pydel!(it) nothing else (v, it) @@ -433,7 +455,6 @@ Base.powermod(x::Py, y::Number, z::Number) = pypow(x, y, z) # documentation function Base.Docs.getdoc(x::Py, @nospecialize(sig)=Union{}) - pyisnull(x) && return nothing parts = [] inspect = pyimport("inspect") # head line diff --git a/src/Core/builtins.jl b/src/Core/builtins.jl index 165e281c..8442f967 100644 --- a/src/Core/builtins.jl +++ b/src/Core/builtins.jl @@ -15,8 +15,8 @@ pyisnot(x, y) = !pyis(x, y) Equivalent to `repr(x)` in Python. """ -pyrepr(x) = pynew(errcheck(@autopy x C.PyObject_Repr(getptr(x_)))) -pyrepr(::Type{String}, x) = (s=pyrepr(x); ans=pystr_asstring(s); pydel!(s); ans) +pyrepr(x) = pynew(errcheck(@autopy x C.PyObject_Repr(x_))) +pyrepr(::Type{String}, x) = (s=pyrepr(x); ans=pystr_asstring(s); unsafe_pydel!(s); ans) export pyrepr """ @@ -24,8 +24,8 @@ export pyrepr Equivalent to `ascii(x)` in Python. """ -pyascii(x) = pynew(errcheck(@autopy x C.PyObject_ASCII(getptr(x_)))) -pyascii(::Type{String}, x) = (s=pyascii(x); ans=pystr_asstring(s); pydel!(s); ans) +pyascii(x) = pynew(errcheck(@autopy x C.PyObject_ASCII(x_))) +pyascii(::Type{String}, x) = (s=pyascii(x); ans=pystr_asstring(s); unsafe_pydel!(s); ans) export pyascii """ @@ -36,7 +36,7 @@ Equivalent to `hasattr(x, k)` in Python. Tests if `getattr(x, k)` raises an `AttributeError`. """ function pyhasattr(x, k) - ptr = @autopy x k C.PyObject_GetAttr(getptr(x_), getptr(k_)) + ptr = @autopy x k C.PyObject_GetAttr(x_, k_) if iserrset(ptr) if errmatches(pybuiltins.AttributeError) errclear() @@ -49,7 +49,7 @@ function pyhasattr(x, k) return true end end -# pyhasattr(x, k) = errcheck(@autopy x k C.PyObject_HasAttr(getptr(x_), getptr(k_))) == 1 +# pyhasattr(x, k) = errcheck(@autopy x k C.PyObject_HasAttr(x_, k_)) == 1 export pyhasattr """ @@ -59,9 +59,9 @@ Equivalent to `getattr(x, k)` or `x.k` in Python. If `d` is specified, it is returned if the attribute does not exist. """ -pygetattr(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetAttr(getptr(x_), getptr(k_)))) +pygetattr(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetAttr(x_, k_))) function pygetattr(x, k, d) - ptr = @autopy x k C.PyObject_GetAttr(getptr(x_), getptr(k_)) + ptr = @autopy x k C.PyObject_GetAttr(x_, k_) if iserrset(ptr) if errmatches(pybuiltins.AttributeError) errclear() @@ -80,7 +80,7 @@ export pygetattr Equivalent to `setattr(x, k, v)` or `x.k = v` in Python. """ -pysetattr(x, k, v) = (errcheck(@autopy x k v C.PyObject_SetAttr(getptr(x_), getptr(k_), getptr(v_))); nothing) +pysetattr(x, k, v) = (errcheck(@autopy x k v C.PyObject_SetAttr(x_, k_, v_)); nothing) export pysetattr """ @@ -88,7 +88,7 @@ export pysetattr Equivalent to `delattr(x, k)` or `del x.k` in Python. """ -pydelattr(x, k) = (errcheck(@autopy x k C.PyObject_SetAttr(getptr(x_), getptr(k_), C.PyNULL)); nothing) +pydelattr(x, k) = (errcheck(@autopy x k C.PyObject_SetAttr(x_, k_, C.PyNULL)); nothing) export pydelattr """ @@ -96,7 +96,7 @@ export pydelattr Test if `s` is a subclass of `t`. Equivalent to `issubclass(s, t)` in Python. """ -pyissubclass(s, t) = errcheck(@autopy s t C.PyObject_IsSubclass(getptr(s_), getptr(t_))) == 1 +pyissubclass(s, t) = errcheck(@autopy s t C.PyObject_IsSubclass(s_, t_)) == 1 export pyissubclass """ @@ -104,7 +104,7 @@ export pyissubclass Test if `x` is of type `t`. Equivalent to `isinstance(x, t)` in Python. """ -pyisinstance(x, t) = errcheck(@autopy x t C.PyObject_IsInstance(getptr(x_), getptr(t_))) == 1 +pyisinstance(x, t) = errcheck(@autopy x t C.PyObject_IsInstance(x_, t_)) == 1 export pyisinstance """ @@ -112,7 +112,7 @@ export pyisinstance Equivalent to `hash(x)` in Python, converted to an `Integer`. """ -pyhash(x) = errcheck(@autopy x C.PyObject_Hash(getptr(x_))) +pyhash(x) = errcheck(@autopy x C.PyObject_Hash(x_)) export pyhash """ @@ -120,7 +120,7 @@ export pyhash The truthyness of `x`. Equivalent to `bool(x)` in Python, converted to a `Bool`. """ -pytruth(x) = errcheck(@autopy x C.PyObject_IsTrue(getptr(x_))) == 1 +pytruth(x) = errcheck(@autopy x C.PyObject_IsTrue(x_)) == 1 export pytruth """ @@ -128,7 +128,7 @@ export pytruth The falsyness of `x`. Equivalent to `not x` in Python, converted to a `Bool`. """ -pynot(x) = errcheck(@autopy x C.PyObject_Not(getptr(x_))) == 1 +pynot(x) = errcheck(@autopy x C.PyObject_Not(x_)) == 1 export pynot """ @@ -136,7 +136,7 @@ export pynot The length of `x`. Equivalent to `len(x)` in Python, converted to an `Integer`. """ -pylen(x) = errcheck(@autopy x C.PyObject_Length(getptr(x_))) +pylen(x) = errcheck(@autopy x C.PyObject_Length(x_)) export pylen """ @@ -145,7 +145,7 @@ export pylen Test if `pygetitem(x, k)` raises a `KeyError` or `AttributeError`. """ function pyhasitem(x, k) - ptr = @autopy x k C.PyObject_GetItem(getptr(x_), getptr(k_)) + ptr = @autopy x k C.PyObject_GetItem(x_, k_) if iserrset(ptr) if errmatches(pybuiltins.KeyError) || errmatches(pybuiltins.IndexError) errclear() @@ -168,9 +168,9 @@ Equivalent `x[k]` in Python. If `d` is specified, it is returned if the item does not exist (i.e. if `x[k]` raises a `KeyError` or `IndexError`). """ -pygetitem(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetItem(getptr(x_), getptr(k_)))) +pygetitem(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetItem(x_, k_))) function pygetitem(x, k, d) - ptr = @autopy x k C.PyObject_GetItem(getptr(x_), getptr(k_)) + ptr = @autopy x k C.PyObject_GetItem(x_, k_) if iserrset(ptr) if errmatches(pybuiltins.KeyError) || errmatches(pybuiltins.IndexError) errclear() @@ -189,7 +189,7 @@ export pygetitem Equivalent to `setitem(x, k, v)` or `x[k] = v` in Python. """ -pysetitem(x, k, v) = (errcheck(@autopy x k v C.PyObject_SetItem(getptr(x_), getptr(k_), getptr(v_))); nothing) +pysetitem(x, k, v) = (errcheck(@autopy x k v C.PyObject_SetItem(x_, k_, v_)); nothing) export pysetitem """ @@ -197,7 +197,7 @@ export pysetitem Equivalent to `delitem(x, k)` or `del x[k]` in Python. """ -pydelitem(x, k) = (errcheck(@autopy x k C.PyObject_DelItem(getptr(x_), getptr(k_))); nothing) +pydelitem(x, k) = (errcheck(@autopy x k C.PyObject_DelItem(x_, k_)); nothing) export pydelitem """ @@ -205,12 +205,12 @@ export pydelitem Equivalent to `dir(x)` in Python. """ -pydir(x) = pynew(errcheck(@autopy x C.PyObject_Dir(getptr(x_)))) +pydir(x) = pynew(errcheck(@autopy x C.PyObject_Dir(x_))) export pydir -pycallargs(f) = pynew(errcheck(@autopy f C.PyObject_CallObject(getptr(f_), C.PyNULL))) -pycallargs(f, args) = pynew(errcheck(@autopy f args C.PyObject_CallObject(getptr(f_), getptr(args_)))) -pycallargs(f, args, kwargs) = pynew(errcheck(@autopy f args kwargs C.PyObject_Call(getptr(f_), getptr(args_), getptr(kwargs_)))) +pycallargs(f) = pynew(errcheck(@autopy f C.PyObject_CallObject(f_, C.PyNULL))) +pycallargs(f, args) = pynew(errcheck(@autopy f args C.PyObject_CallObject(f_, args_))) +pycallargs(f, args, kwargs) = pynew(errcheck(@autopy f args kwargs C.PyObject_Call(f_, args_, kwargs_))) """ pycall(f, args...; kwargs...) @@ -222,13 +222,13 @@ pycall(f, args...; kwargs...) = args_ = pytuple_fromiter(args) kwargs_ = pystrdict_fromiter(kwargs) ans = pycallargs(f, args_, kwargs_) - pydel!(args_) - pydel!(kwargs_) + unsafe_pydel!(args_) + unsafe_pydel!(kwargs_) ans elseif !isempty(args) args_ = pytuple_fromiter(args) ans = pycallargs(f, args_) - pydel!(args_) + unsafe_pydel!(args_) ans else pycallargs(f) @@ -241,7 +241,7 @@ export pycall Equivalent to `x == y` in Python. The second form converts to `Bool`. """ -pyeq(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_EQ))) +pyeq(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_EQ))) """ pyne(x, y) @@ -249,7 +249,7 @@ pyeq(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getpt Equivalent to `x != y` in Python. The second form converts to `Bool`. """ -pyne(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_NE))) +pyne(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_NE))) """ pyle(x, y) @@ -257,7 +257,7 @@ pyne(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getpt Equivalent to `x <= y` in Python. The second form converts to `Bool`. """ -pyle(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_LE))) +pyle(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_LE))) """ pylt(x, y) @@ -265,7 +265,7 @@ pyle(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getpt Equivalent to `x < y` in Python. The second form converts to `Bool`. """ -pylt(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_LT))) +pylt(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_LT))) """ pyge(x, y) @@ -273,7 +273,7 @@ pylt(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getpt Equivalent to `x >= y` in Python. The second form converts to `Bool`. """ -pyge(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_GE))) +pyge(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_GE))) """ pygt(x, y) @@ -281,13 +281,13 @@ pyge(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getpt Equivalent to `x > y` in Python. The second form converts to `Bool`. """ -pygt(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(getptr(x_), getptr(y_), C.Py_GT))) -pyeq(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_EQ)) == 1 -pyne(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_NE)) == 1 -pyle(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_LE)) == 1 -pylt(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_LT)) == 1 -pyge(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_GE)) == 1 -pygt(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(getptr(x_), getptr(y_), C.Py_GT)) == 1 +pygt(x, y) = pynew(errcheck(@autopy x y C.PyObject_RichCompare(x_, y_, C.Py_GT))) +pyeq(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_EQ)) == 1 +pyne(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_NE)) == 1 +pyle(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_LE)) == 1 +pylt(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_LT)) == 1 +pyge(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_GE)) == 1 +pygt(::Type{Bool}, x, y) = errcheck(@autopy x y C.PyObject_RichCompareBool(x_, y_, C.Py_GT)) == 1 export pyeq, pyne, pyle, pylt, pyge, pygt """ @@ -295,7 +295,7 @@ export pyeq, pyne, pyle, pylt, pyge, pygt Equivalent to `v in x` in Python. """ -pycontains(x, v) = errcheck(@autopy x v C.PySequence_Contains(getptr(x_), getptr(v_))) == 1 +pycontains(x, v) = errcheck(@autopy x v C.PySequence_Contains(x_, v_)) == 1 export pycontains """ @@ -316,31 +316,31 @@ pynotin(v, x) = !pyin(v, x) Equivalent to `-x` in Python. """ -pyneg(x) = pynew(errcheck(@autopy x C.PyNumber_Negative(getptr(x_)))) +pyneg(x) = pynew(errcheck(@autopy x C.PyNumber_Negative(x_))) """ pypos(x) Equivalent to `+x` in Python. """ -pypos(x) = pynew(errcheck(@autopy x C.PyNumber_Positive(getptr(x_)))) +pypos(x) = pynew(errcheck(@autopy x C.PyNumber_Positive(x_))) """ pyabs(x) Equivalent to `abs(x)` in Python. """ -pyabs(x) = pynew(errcheck(@autopy x C.PyNumber_Absolute(getptr(x_)))) +pyabs(x) = pynew(errcheck(@autopy x C.PyNumber_Absolute(x_))) """ pyinv(x) Equivalent to `~x` in Python. """ -pyinv(x) = pynew(errcheck(@autopy x C.PyNumber_Invert(getptr(x_)))) +pyinv(x) = pynew(errcheck(@autopy x C.PyNumber_Invert(x_))) """ pyindex(x) Convert `x` losslessly to an `int`. """ -pyindex(x) = pynew(errcheck(@autopy x C.PyNumber_Index(getptr(x_)))) +pyindex(x) = pynew(errcheck(@autopy x C.PyNumber_Index(x_))) export pyneg, pypos, pyabs, pyinv, pyindex # binary @@ -349,79 +349,79 @@ export pyneg, pypos, pyabs, pyinv, pyindex Equivalent to `x + y` in Python. """ -pyadd(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Add(getptr(x_), getptr(y_)))) +pyadd(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Add(x_, y_))) """ pysub(x, y) Equivalent to `x - y` in Python. """ -pysub(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Subtract(getptr(x_), getptr(y_)))) +pysub(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Subtract(x_, y_))) """ pymul(x, y) Equivalent to `x * y` in Python. """ -pymul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Multiply(getptr(x_), getptr(y_)))) +pymul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Multiply(x_, y_))) """ pymatmul(x, y) Equivalent to `x @ y` in Python. """ -pymatmul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_MatrixMultiply(getptr(x_), getptr(y_)))) +pymatmul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_MatrixMultiply(x_, y_))) """ pyfloordiv(x, y) Equivalent to `x // y` in Python. """ -pyfloordiv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_FloorDivide(getptr(x_), getptr(y_)))) +pyfloordiv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_FloorDivide(x_, y_))) """ pytruediv(x, y) Equivalent to `x / y` in Python. """ -pytruediv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_TrueDivide(getptr(x_), getptr(y_)))) +pytruediv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_TrueDivide(x_, y_))) """ pymod(x, y) Equivalent to `x % y` in Python. """ -pymod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Remainder(getptr(x_), getptr(y_)))) +pymod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Remainder(x_, y_))) """ pydivmod(x, y) Equivalent to `divmod(x, y)` in Python. """ -pydivmod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Divmod(getptr(x_), getptr(y_)))) +pydivmod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Divmod(x_, y_))) """ pylshift(x, y) Equivalent to `x << y` in Python. """ -pylshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Lshift(getptr(x_), getptr(y_)))) +pylshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Lshift(x_, y_))) """ pyrshift(x, y) Equivalent to `x >> y` in Python. """ -pyrshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Rshift(getptr(x_), getptr(y_)))) +pyrshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Rshift(x_, y_))) """ pyand(x, y) Equivalent to `x & y` in Python. """ -pyand(x, y) = pynew(errcheck(@autopy x y C.PyNumber_And(getptr(x_), getptr(y_)))) +pyand(x, y) = pynew(errcheck(@autopy x y C.PyNumber_And(x_, y_))) """ pyxor(x, y) Equivalent to `x ^ y` in Python. """ -pyxor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Xor(getptr(x_), getptr(y_)))) +pyxor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Xor(x_, y_))) """ pyor(x, y) Equivalent to `x | y` in Python. """ -pyor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Or(getptr(x_), getptr(y_)))) +pyor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_Or(x_, y_))) export pyadd, pysub, pymul, pymatmul, pyfloordiv, pytruediv, pymod, pydivmod, pylshift, pyrshift, pyand, pyxor, pyor # binary in-place @@ -430,73 +430,73 @@ export pyadd, pysub, pymul, pymatmul, pyfloordiv, pytruediv, pymod, pydivmod, py In-place add. `x = pyiadd(x, y)` is equivalent to `x += y` in Python. """ -pyiadd(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceAdd(getptr(x_), getptr(y_)))) +pyiadd(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceAdd(x_, y_))) """ pyisub(x, y) In-place subtract. `x = pyisub(x, y)` is equivalent to `x -= y` in Python. """ -pyisub(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceSubtract(getptr(x_), getptr(y_)))) +pyisub(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceSubtract(x_, y_))) """ pyimul(x, y) In-place multiply. `x = pyimul(x, y)` is equivalent to `x *= y` in Python. """ -pyimul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceMultiply(getptr(x_), getptr(y_)))) +pyimul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceMultiply(x_, y_))) """ pyimatmul(x, y) In-place matrix multiply. `x = pyimatmul(x, y)` is equivalent to `x @= y` in Python. """ -pyimatmul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceMatrixMultiply(getptr(x_), getptr(y_)))) +pyimatmul(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceMatrixMultiply(x_, y_))) """ pyifloordiv(x, y) In-place floor divide. `x = pyifloordiv(x, y)` is equivalent to `x //= y` in Python. """ -pyifloordiv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceFloorDivide(getptr(x_), getptr(y_)))) +pyifloordiv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceFloorDivide(x_, y_))) """ pyitruediv(x, y) In-place true division. `x = pyitruediv(x, y)` is equivalent to `x /= y` in Python. """ -pyitruediv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceTrueDivide(getptr(x_), getptr(y_)))) +pyitruediv(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceTrueDivide(x_, y_))) """ pyimod(x, y) In-place subtraction. `x = pyimod(x, y)` is equivalent to `x %= y` in Python. """ -pyimod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceRemainder(getptr(x_), getptr(y_)))) +pyimod(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceRemainder(x_, y_))) """ pyilshift(x, y) In-place left shift. `x = pyilshift(x, y)` is equivalent to `x <<= y` in Python. """ -pyilshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceLshift(getptr(x_), getptr(y_)))) +pyilshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceLshift(x_, y_))) """ pyirshift(x, y) In-place right shift. `x = pyirshift(x, y)` is equivalent to `x >>= y` in Python. """ -pyirshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceRshift(getptr(x_), getptr(y_)))) +pyirshift(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceRshift(x_, y_))) """ pyiand(x, y) In-place and. `x = pyiand(x, y)` is equivalent to `x &= y` in Python. """ -pyiand(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceAnd(getptr(x_), getptr(y_)))) +pyiand(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceAnd(x_, y_))) """ pyixor(x, y) In-place xor. `x = pyixor(x, y)` is equivalent to `x ^= y` in Python. """ -pyixor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceXor(getptr(x_), getptr(y_)))) +pyixor(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceXor(x_, y_))) """ pyior(x, y) In-place or. `x = pyior(x, y)` is equivalent to `x |= y` in Python. """ -pyior(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceOr(getptr(x_), getptr(y_)))) +pyior(x, y) = pynew(errcheck(@autopy x y C.PyNumber_InPlaceOr(x_, y_))) export pyiadd, pyisub, pyimul, pyimatmul, pyifloordiv, pyitruediv, pyimod, pyilshift, pyirshift, pyiand, pyixor, pyior # power @@ -505,13 +505,13 @@ export pyiadd, pyisub, pyimul, pyimatmul, pyifloordiv, pyitruediv, pyimod, pyils Equivalent to `x ** y` or `pow(x, y, z)` in Python. """ -pypow(x, y, z=pybuiltins.None) = pynew(errcheck(@autopy x y z C.PyNumber_Power(getptr(x_), getptr(y_), getptr(z_)))) +pypow(x, y, z=pybuiltins.None) = pynew(errcheck(@autopy x y z C.PyNumber_Power(x_, y_, z_))) """ pyipow(x, y, z=None) In-place power. `x = pyipow(x, y)` is equivalent to `x **= y` in Python. """ -pyipow(x, y, z=pybuiltins.None) = pynew(errcheck(@autopy x y z C.PyNumber_InPlacePower(getptr(x_), getptr(y_), getptr(z_)))) +pyipow(x, y, z=pybuiltins.None) = pynew(errcheck(@autopy x y z C.PyNumber_InPlacePower(x_, y_, z_))) export pypow, pyipow ### iter @@ -521,7 +521,7 @@ export pypow, pyipow Equivalent to `iter(x)` in Python. """ -pyiter(x) = pynew(errcheck(@autopy x C.PyObject_GetIter(getptr(x_)))) +pyiter(x) = pynew(errcheck(@autopy x C.PyObject_GetIter(x_))) export pyiter """ @@ -537,7 +537,8 @@ export pynext Return the next item in the iterator `x`. When there are no more items, return NULL. """ -unsafe_pynext(x::Py) = Base.GC.@preserve x pynew(errcheck_ambig(C.PyIter_Next(getptr(x)))) +unsafe_pynext(x::Py) = Base.GC.@preserve x pynew(errcheck_ambig(C.PyIter_Next(x))) +export unsafe_pynext ### None @@ -579,21 +580,21 @@ pystr_fromUTF8(x) = pystr_fromUTF8(pointer(x), sizeof(x)) Convert `x` to a Python `str`. """ -pystr(x) = pynew(errcheck(@autopy x C.PyObject_Str(getptr(x_)))) +pystr(x) = pynew(errcheck(@autopy x C.PyObject_Str(x_))) pystr(x::String) = pystr_fromUTF8(x) pystr(x::SubString{String}) = pystr_fromUTF8(x) pystr(x::Char) = pystr(string(x)) -pystr(::Type{String}, x) = (s=pystr(x); ans=pystr_asstring(s); pydel!(s); ans) +pystr(::Type{String}, x) = (s=pystr(x); ans=pystr_asstring(s); unsafe_pydel!(s); ans) export pystr -pystr_asUTF8bytes(x::Py) = Base.GC.@preserve x pynew(errcheck(C.PyUnicode_AsUTF8String(getptr(x)))) -pystr_asUTF8vector(x::Py) = (b=pystr_asUTF8bytes(x); ans=pybytes_asvector(b); pydel!(b); ans) -pystr_asstring(x::Py) = (b=pystr_asUTF8bytes(x); ans=pybytes_asUTF8string(b); pydel!(b); ans) +pystr_asUTF8bytes(x::Py) = Base.GC.@preserve x pynew(errcheck(C.PyUnicode_AsUTF8String(x))) +pystr_asUTF8vector(x::Py) = (b=pystr_asUTF8bytes(x); ans=pybytes_asvector(b); unsafe_pydel!(b); ans) +pystr_asstring(x::Py) = (b=pystr_asUTF8bytes(x); ans=pybytes_asUTF8string(b); unsafe_pydel!(b); ans) function pystr_intern!(x::Py) ptr = Ref(getptr(x)) C.PyUnicode_InternInPlace(ptr) - setptr!(x, ptr[]) + unsafe_setptr!(x, ptr[]) end pyisstr(x) = pytypecheckfast(x, C.Py_TPFLAGS_UNICODE_SUBCLASS) @@ -608,12 +609,12 @@ pybytes_fromdata(x) = pybytes_fromdata(pointer(x), sizeof(x)) Convert `x` to a Python `bytes`. """ -pybytes(x) = pynew(errcheck(@autopy x C.PyObject_Bytes(getptr(x_)))) +pybytes(x) = pynew(errcheck(@autopy x C.PyObject_Bytes(x_))) pybytes(x::Vector{UInt8}) = pybytes_fromdata(x) pybytes(x::Base.CodeUnits{UInt8, String}) = pybytes_fromdata(x) pybytes(x::Base.CodeUnits{UInt8, SubString{String}}) = pybytes_fromdata(x) -pybytes(::Type{T}, x) where {Vector{UInt8} <: T <: Vector} = (b=pybytes(x); ans=pybytes_asvector(b); pydel!(b); ans) -pybytes(::Type{T}, x) where {Base.CodeUnits{UInt8,String} <: T <: Base.CodeUnits} = (b=pybytes(x); ans=Base.CodeUnits(pybytes_asUTF8string(b)); pydel!(b); ans) +pybytes(::Type{T}, x) where {Vector{UInt8} <: T <: Vector} = (b=pybytes(x); ans=pybytes_asvector(b); unsafe_pydel!(b); ans) +pybytes(::Type{T}, x) where {Base.CodeUnits{UInt8,String} <: T <: Base.CodeUnits} = (b=pybytes(x); ans=Base.CodeUnits(pybytes_asUTF8string(b)); unsafe_pydel!(b); ans) export pybytes pyisbytes(x) = pytypecheckfast(x, C.Py_TPFLAGS_BYTES_SUBCLASS) @@ -621,7 +622,7 @@ pyisbytes(x) = pytypecheckfast(x, C.Py_TPFLAGS_BYTES_SUBCLASS) function pybytes_asdata(x::Py) ptr = Ref(Ptr{Cchar}(0)) len = Ref(C.Py_ssize_t(0)) - Base.GC.@preserve x errcheck(C.PyBytes_AsStringAndSize(getptr(x), ptr, len)) + Base.GC.@preserve x errcheck(C.PyBytes_AsStringAndSize(x, ptr, len)) ptr[], len[] end @@ -662,7 +663,7 @@ function pyint(x::Unsigned) pyint_fallback(x) end end -pyint(x) = @autopy x pynew(errcheck(C.PyNumber_Long(getptr(x_)))) +pyint(x) = @autopy x pynew(errcheck(C.PyNumber_Long(x_))) export pyint pyisint(x) = pytypecheckfast(x, C.Py_TPFLAGS_LONG_SUBCLASS) @@ -675,12 +676,12 @@ pyisint(x) = pytypecheckfast(x, C.Py_TPFLAGS_LONG_SUBCLASS) Convert `x` to a Python `float`. """ pyfloat(x::Real=0.0) = pynew(errcheck(C.PyFloat_FromDouble(x))) -pyfloat(x) = @autopy x pynew(errcheck(C.PyNumber_Float(getptr(x_)))) +pyfloat(x) = @autopy x pynew(errcheck(C.PyNumber_Float(x_))) export pyfloat pyisfloat(x) = pytypecheck(x, pybuiltins.float) -pyfloat_asdouble(x) = errcheck_ambig(@autopy x C.PyFloat_AsDouble(getptr(x_))) +pyfloat_asdouble(x) = errcheck_ambig(@autopy x C.PyFloat_AsDouble(x_)) ### complex @@ -699,7 +700,7 @@ export pycomplex pyiscomplex(x) = pytypecheck(x, pybuiltins.complex) function pycomplex_ascomplex(x) - c = @autopy x C.PyComplex_AsCComplex(getptr(x_)) + c = @autopy x C.PyComplex_AsCComplex(x_) c.real == -1 && c.imag == 0 && errcheck() return Complex(c.real, c.imag) end @@ -711,7 +712,7 @@ end The Python `type` of `x`. """ -pytype(x) = pynew(errcheck(@autopy x C.PyObject_Type(getptr(x_)))) +pytype(x) = pynew(errcheck(@autopy x C.PyObject_Type(x_))) export pytype """ @@ -787,8 +788,8 @@ end pyistype(x) = pytypecheckfast(x, C.Py_TPFLAGS_TYPE_SUBCLASS) -pytypecheck(x, t) = (@autopy x t C.Py_TypeCheck(getptr(x_), getptr(t_))) == 1 -pytypecheckfast(x, f) = (@autopy x C.Py_TypeCheckFast(getptr(x_), f)) == 1 +pytypecheck(x, t) = (@autopy x t C.Py_TypeCheck(x_, t_)) == 1 +pytypecheckfast(x, f) = (@autopy x C.Py_TypeCheckFast(x_, f)) == 1 ### slice @@ -797,7 +798,7 @@ pytypecheckfast(x, f) = (@autopy x C.Py_TypeCheckFast(getptr(x_), f)) == 1 Construct a Python `slice`. Unspecified arguments default to `None`. """ -pyslice(x, y, z=pybuiltins.None) = pynew(errcheck(@autopy x y z C.PySlice_New(getptr(x_), getptr(y_), getptr(z_)))) +pyslice(x, y, z=pybuiltins.None) = pynew(errcheck(@autopy x y z C.PySlice_New(x_, y_, z_))) pyslice(y) = pyslice(pybuiltins.None, y, pybuiltins.None) export pyslice @@ -824,12 +825,12 @@ pyisrange(x) = pytypecheck(x, pybuiltins.range) pynulltuple(len) = pynew(errcheck(C.PyTuple_New(len))) function pytuple_setitem(xs::Py, i, x) - errcheck(C.PyTuple_SetItem(getptr(xs), i, incref(getptr(Py(x))))) + errcheck(C.PyTuple_SetItem(xs, i, incref(getptr(Py(x))))) return xs end function pytuple_getitem(xs::Py, i) - Base.GC.@preserve xs pynew(incref(errcheck(C.PyTuple_GetItem(getptr(xs), i)))) + Base.GC.@preserve xs pynew(incref(errcheck(C.PyTuple_GetItem(xs, i)))) end function pytuple_fromiter(xs) @@ -845,7 +846,7 @@ function pytuple_fromiter(xs) # length unknown xs_ = pylist_fromiter(xs) ans = pylist_astuple(xs_) - pydel!(xs_) + unsafe_pydel!(xs_) return ans end end @@ -880,13 +881,13 @@ pyistuple(x) = pytypecheckfast(x, C.Py_TPFLAGS_TUPLE_SUBCLASS) pynulllist(len) = pynew(errcheck(C.PyList_New(len))) function pylist_setitem(xs::Py, i, x) - errcheck(C.PyList_SetItem(getptr(xs), i, incref(getptr(Py(x))))) + errcheck(C.PyList_SetItem(xs, i, incref(getptr(Py(x))))) return xs end -pylist_append(xs::Py, x) = errcheck(@autopy x C.PyList_Append(getptr(xs), getptr(x_))) +pylist_append(xs::Py, x) = errcheck(@autopy x C.PyList_Append(xs, x_)) -pylist_astuple(x) = pynew(errcheck(@autopy x C.PyList_AsTuple(getptr(x_)))) +pylist_astuple(x) = pynew(errcheck(@autopy x C.PyList_AsTuple(x_))) function pylist_fromiter(xs) sz = Base.IteratorSize(typeof(xs)) @@ -932,7 +933,7 @@ function pycollist(x::AbstractArray{T,N}) where {T,N} for (i, j) in enumerate(ax) y = pycollist(selectdim(x, d, j)) pylist_setitem(ans, i-1, y) - pydel!(y) + unsafe_pydel!(y) end return ans end @@ -951,7 +952,7 @@ function pyrowlist(x::AbstractArray{T,N}) where {T,N} for (i, j) in enumerate(ax) y = pyrowlist(selectdim(x, d, j)) pylist_setitem(ans, i-1, y) - pydel!(y) + unsafe_pydel!(y) end return ans end @@ -959,7 +960,7 @@ export pyrowlist ### set -pyset_add(set::Py, x) = (errcheck(@autopy x C.PySet_Add(getptr(set), getptr(x_))); set) +pyset_add(set::Py, x) = (errcheck(@autopy x C.PySet_Add(set, x_)); set) function pyset_update_fromiter(set::Py, xs) for x in xs @@ -996,7 +997,7 @@ export pyfrozenset ### dict -pydict_setitem(x::Py, k, v) = errcheck(@autopy k v C.PyDict_SetItem(getptr(x), getptr(k_), getptr(v_))) +pydict_setitem(x::Py, k, v) = errcheck(@autopy k v C.PyDict_SetItem(x, k_, v_)) function pydict_fromiter(kvs) ans = pydict() @@ -1059,7 +1060,7 @@ function pydatetime(x::DateTime) # this accounts for fold d = pytimedeltatype(milliseconds = (x - _base_datetime).value) ans = _base_pydatetime + d - pydel!(d) + unsafe_pydel!(d) return ans end pydatetime(x::Date) = pydatetime(year(x), month(x), day(x)) @@ -1068,30 +1069,30 @@ export pydatetime function pytime_isaware(x) tzinfo = pygetattr(x, "tzinfo") if pyisnone(tzinfo) - pydel!(tzinfo) + unsafe_pydel!(tzinfo) return false end utcoffset = tzinfo.utcoffset - pydel!(tzinfo) + unsafe_pydel!(tzinfo) o = utcoffset(nothing) - pydel!(utcoffset) + unsafe_pydel!(utcoffset) ans = !pyisnone(o) - pydel!(o) + unsafe_pydel!(o) return ans end function pydatetime_isaware(x) tzinfo = pygetattr(x, "tzinfo") if pyisnone(tzinfo) - pydel!(tzinfo) + unsafe_pydel!(tzinfo) return false end utcoffset = tzinfo.utcoffset - pydel!(tzinfo) + unsafe_pydel!(tzinfo) o = utcoffset(x) - pydel!(utcoffset) + unsafe_pydel!(utcoffset) ans = !pyisnone(o) - pydel!(o) + unsafe_pydel!(o) return ans end @@ -1155,7 +1156,7 @@ pyeval(Float64, "x+y", Main, (x=1.1, y=2.2)) # returns 3.3 function pyeval(::Type{T}, code, globals, locals=nothing) where {T} code_, globals_, locals_ = _pyeval_args(code, globals, locals) ans = pybuiltins.eval(code_, globals_, locals_) - pydel!(locals_) + unsafe_pydel!(locals_) return pyconvert(T, ans) end pyeval(code, globals, locals=nothing) = pyeval(Py, code, globals, locals) @@ -1209,9 +1210,9 @@ pyeval(Int, "x", Main) # returns 12 """ function pyexec(::Type{T}, code, globals, locals=nothing) where {T} code_, globals_, locals_ = _pyeval_args(code, globals, locals) - pydel!(pybuiltins.exec(code_, globals_, locals_)) + unsafe_pydel!(pybuiltins.exec(code_, globals_, locals_)) ans = _pyexec_ans(T, globals_, locals_) - pydel!(locals_) + unsafe_pydel!(locals_) return ans end pyexec(code, globals, locals=nothing) = pyexec(Nothing, code, globals, locals) @@ -1441,9 +1442,9 @@ Import a module `m`, or an attribute `k`, or a tuple of attributes. If several arguments are given, return the results of importing each one in a tuple. """ -pyimport(m) = pynew(errcheck(@autopy m C.PyImport_Import(getptr(m_)))) -pyimport((m,k)::Pair) = (m_=pyimport(m); k_=pygetattr(m_,k); pydel!(m_); k_) -pyimport((m,ks)::Pair{<:Any,<:Tuple}) = (m_=pyimport(m); ks_=map(k->pygetattr(m_,k), ks); pydel!(m_); ks_) +pyimport(m) = pynew(errcheck(@autopy m C.PyImport_Import(m_))) +pyimport((m,k)::Pair) = (m_=pyimport(m); k_=pygetattr(m_,k); unsafe_pydel!(m_); k_) +pyimport((m,ks)::Pair{<:Any,<:Tuple}) = (m_=pyimport(m); ks_=map(k->pygetattr(m_,k), ks); unsafe_pydel!(m_); ks_) pyimport(m1, m2, ms...) = map(pyimport, (m1, m2, ms...)) export pyimport @@ -1454,12 +1455,12 @@ export pyimport Equivalent to `print(...)` in Python. """ -pyprint(args...; kwargs...) = (pydel!(pybuiltins.print(args...; kwargs...)); nothing) +pyprint(args...; kwargs...) = (unsafe_pydel!(pybuiltins.print(args...; kwargs...)); nothing) export pyprint function _pyhelp(args...) pyisnone(pybuiltins.help) && error("Python help is not available") - pydel!(pybuiltins.help(args...)) + unsafe_pydel!(pybuiltins.help(args...)) nothing end """ @@ -1479,7 +1480,7 @@ Equivalent to `all(x)` in Python. function pyall(x) y = pybuiltins.all(x) z = pybool_asbool(y) - pydel!(y) + unsafe_pydel!(y) z end export pyall @@ -1492,7 +1493,7 @@ Equivalent to `any(x)` in Python. function pyany(x) y = pybuiltins.any(x) z = pybool_asbool(y) - pydel!(y) + unsafe_pydel!(y) z end export pyany @@ -1505,7 +1506,7 @@ Equivalent to `callable(x)` in Python. function pycallable(x) y = pybuiltins.callable(x) z = pybool_asbool(y) - pydel!(y) + unsafe_pydel!(y) z end export pycallable diff --git a/src/Core/err.jl b/src/Core/err.jl index 37f67748..eded75fe 100644 --- a/src/Core/err.jl +++ b/src/Core/err.jl @@ -15,7 +15,7 @@ errcheck_ambig(val) = iserrset_ambig(val) ? pythrow() : val errclear() = C.PyErr_Clear() -errmatches(t) = (@autopy t C.PyErr_ExceptionMatches(getptr(t_))) == 1 +errmatches(t) = (@autopy t C.PyErr_ExceptionMatches(t_)) == 1 function errget() t = Ref(C.PyNULL) @@ -25,9 +25,9 @@ function errget() (pynew(t[]), pynew(v[]), pynew(b[])) end -errset(t::Py) = Base.GC.@preserve t C.PyErr_SetNone(getptr(t)) -errset(t::Py, v::Py) = Base.GC.@preserve t v C.PyErr_SetObject(getptr(t), getptr(v)) -errset(t::Py, v::String) = Base.GC.@preserve t C.PyErr_SetString(getptr(t), v) +errset(t::Py) = Base.GC.@preserve t C.PyErr_SetNone(t) +errset(t::Py, v::Py) = Base.GC.@preserve t v C.PyErr_SetObject(t, v) +errset(t::Py, v::String) = Base.GC.@preserve t C.PyErr_SetString(t, v) function errnormalize!(t::Py, v::Py, b::Py) tptr = getptr(t) @@ -37,9 +37,9 @@ function errnormalize!(t::Py, v::Py, b::Py) vref = Ref(vptr) bref = Ref(bptr) C.PyErr_NormalizeException(tref, vref, bref) - setptr!(t, tref[]) - setptr!(v, vref[]) - setptr!(b, bref[]) + unsafe_setptr!(t, tref[]) + unsafe_setptr!(v, vref[]) + unsafe_setptr!(b, bref[]) (t, v, b) end @@ -80,9 +80,9 @@ end function Base.getproperty(exc::PyException, k::Symbol) if k in (:t, :v, :b) && !exc._isnormalized errnormalize!(exc._t, exc._v, exc._b) - pyisnull(exc._t) && setptr!(exc._t, incref(getptr(pybuiltins.None))) - pyisnull(exc._v) && setptr!(exc._v, incref(getptr(pybuiltins.None))) - pyisnull(exc._b) && setptr!(exc._b, incref(getptr(pybuiltins.None))) + pyisnew(exc._t) && unsafe_setptr!(exc._t, incref(getptr(pybuiltins.None))) + pyisnew(exc._v) && unsafe_setptr!(exc._v, incref(getptr(pybuiltins.None))) + pyisnew(exc._b) && unsafe_setptr!(exc._b, incref(getptr(pybuiltins.None))) pyisnone(exc._v) || (exc._v.__traceback__ = exc._b) exc._isnormalized = true end @@ -130,7 +130,7 @@ function _showerror(io::IO, e::PyException, bt; backtrace=true) end end - if !pyisnull(pyJuliaError) && pyissubclass(e.t, pyJuliaError) + if !pyisnew(pyJlError) && pyissubclass(e.t, pyJlError) # handle Julia exceptions specially try je, jb = pyconvert(Tuple{Any,Any}, e.v.args) diff --git a/src/Core/juliacall.jl b/src/Core/juliacall.jl index 15945f6b..9f6f63e7 100644 --- a/src/Core/juliacall.jl +++ b/src/Core/juliacall.jl @@ -1,6 +1,6 @@ const pyjuliacallmodule = pynew() -const pyJuliaError = pynew() -const CPyExc_JuliaError = Ref(C.PyNULL) +const pyJlError = pynew() +const CPyExc_JlError = Ref(C.PyNULL) function init_juliacall() # ensure the 'juliacall' module exists @@ -29,6 +29,6 @@ function init_juliacall() @assert pystr_asstring(jl.__version__) == string(VERSION) @assert !pybool_asbool(jl.CONFIG["init"]) end - pycopy!(pyJuliaError, jl.JuliaError) - CPyExc_JuliaError[] = incref(getptr(pyJuliaError)) + pycopy!(pyJlError, jl.JlError) + CPyExc_JlError[] = incref(getptr(pyJlError)) end diff --git a/src/Core/pyconst_macro.jl b/src/Core/pyconst_macro.jl index e4dd216d..448db346 100644 --- a/src/Core/pyconst_macro.jl +++ b/src/Core/pyconst_macro.jl @@ -19,6 +19,6 @@ macro pyconst(ex) else val = :($Py($val)) end - :(pyisnull($x) ? pycopy!($x, $val) : $x) + :(pyisnew($x) ? pycopy!($x, $val) : $x) end export @pyconst diff --git a/src/Core/stdlib.jl b/src/Core/stdlib.jl index c1157539..b6303024 100644 --- a/src/Core/stdlib.jl +++ b/src/Core/stdlib.jl @@ -14,7 +14,7 @@ function init_stdlib() push!(Base.Core.ARGS, joinpath(ROOT_DIR, "pysrc", "juliacall", "init.jl")) Base._start() Base.eval(:(PROGRAM_FILE = "")) - + # if Python is interactive, ensure Julia is too if pyhasattr(pysysmodule, "ps1") Base.eval(:(is_interactive = true)) diff --git a/src/GC/GC.jl b/src/GC/GC.jl index 0d1fa9a8..7b568ac6 100644 --- a/src/GC/GC.jl +++ b/src/GC/GC.jl @@ -3,7 +3,7 @@ Garbage collection of Python objects. -See `disable` and `enable`. +See [`enable`](@ref) and [`gc`](@ref). """ module GC @@ -13,32 +13,39 @@ const ENABLED = Ref(true) const QUEUE = C.PyPtr[] """ - PythonCall.GC.disable() + PythonCall.GC.enable(on::Bool) -Disable the PythonCall garbage collector. +Control whether garbage collection of Python objects is turned on or off. -This means that whenever a Python object owned by Julia is finalized, it is not immediately -freed but is instead added to a queue of objects to free later when `enable()` is called. +Return the previous GC state. + +Disabling the GC means that whenever a Python object owned by Julia is finalized, it is not +immediately freed but is instead added to a queue of objects to free later when GC is +re-enabled. Like most PythonCall functions, you must only call this from the main thread. """ -function disable() - ENABLED[] = false - return +function enable(on::Bool) + was_on = ENABLED[] + if on + ENABLED[] = true + if !was_on + gc() + end + else + ENABLED[] = false + end + return was_on end """ - PythonCall.GC.enable() - -Re-enable the PythonCall garbage collector. + PythonCall.GC.gc() -This frees any Python objects which were finalized while the GC was disabled, and allows -objects finalized in the future to be freed immediately. +Perform garbage collection of Python objects. Like most PythonCall functions, you must only call this from the main thread. """ -function enable() - ENABLED[] = true +function gc() if !isempty(QUEUE) C.with_gil(false) do for ptr in QUEUE @@ -47,9 +54,8 @@ function enable() end end end + empty!(QUEUE) end - empty!(QUEUE) - return end function enqueue(ptr::C.PyPtr) diff --git a/src/JlCore/C.jl b/src/JlCore/C.jl new file mode 100644 index 00000000..4cce68af --- /dev/null +++ b/src/JlCore/C.jl @@ -0,0 +1,1141 @@ +module JlC + +using Base +using ...Utils: Utils +using ...C: C +using ...Core +using ...Core: getptr, incref, errset, errmatches, errclear, pyisstr, pystr_asstring, pyJlError, pyistuple, pyisnone, pyisstr, pystr_asstring +using ...Convert: pyconvert +using ..JlCore: pyjl +using Base: @kwdef +using UnsafePointers: UnsafePtr +# using Serialization: serialize, deserialize + +@kwdef struct PyJuliaValueObject + ob_base::C.PyObject = C.PyObject() + value::Int = 0 + weaklist::C.PyPtr = C_NULL +end + +const PyJl_Type = Ref(C.PyNULL) + +# we store the actual julia values here +# the `value` field of `PyJuliaValueObject` indexes into here +const PYJLVALUES = [] +# unused indices in PYJLVALUES +const PYJLFREEVALUES = Int[] + +function _pyjl_new(t::C.PyPtr, ::C.PyPtr, ::C.PyPtr) + o = ccall(UnsafePtr{C.PyTypeObject}(t).alloc[!], C.PyPtr, (C.PyPtr, C.Py_ssize_t), t, 0) + o == C.PyNULL && return C.PyNULL + UnsafePtr{PyJuliaValueObject}(o).weaklist[] = C.PyNULL + UnsafePtr{PyJuliaValueObject}(o).value[] = 0 + return o +end + +function _pyjl_dealloc(o::C.PyPtr) + idx = UnsafePtr{PyJuliaValueObject}(o).value[] + if idx != 0 + PYJLVALUES[idx] = nothing + push!(PYJLFREEVALUES, idx) + end + UnsafePtr{PyJuliaValueObject}(o).weaklist[!] == C.PyNULL || C.PyObject_ClearWeakRefs(o) + ccall(UnsafePtr{C.PyTypeObject}(C.Py_Type(o)).free[!], Cvoid, (C.PyPtr,), o) + nothing +end + +function _return(x::Py; del::Bool=false) + ptr = incref(getptr(x)) + del && unsafe_pydel!(x) + ptr +end + +function _return(x, t::Symbol) + if t === :jl + PyJl_New(x) + elseif t === :none + x::Nothing + incref(C.POINTERS._Py_NoneStruct) + elseif t === :bool + x::Bool + incref(x ? C.POINTERS._Py_TrueStruct : C.POINTERS._Py_FalseStruct) + elseif t === :str + x::AbstractString + xstr = convert(String, x)::String + C.PyUnicode_DecodeUTF8(pointer(xstr), sizeof(xstr), C_NULL) + elseif t === :int + x::Integer + C.PyLong_FromLongLong(x) + elseif t === :float + x::Real + C.PyFloat_FromDouble(x) + elseif t === :complex + x::Number + C.PyComplex_FromDoubles(real(x), imag(x)) + elseif t === :any + _return(Py(x)) + else + error("invalid t=$t") + end +end + +function _raise(exc) + try + errset(pyJlError, pytuple((pyjl(exc), pyjl(catch_backtrace())))) + catch + @debug "Julia exception" exc + errset(pybuiltins.Exception, "an error occurred while raising a Julia error") + end + nothing +end + +function _getany(ptr::C.PyPtr) + if PyJl_Check(ptr) + PyJl_GetValue(ptr) + else + pyconvert(Any, pynew(incref(ptr))) + end +end + +function _getany(::Type{T}, ptr::C.PyPtr) where {T} + if PyJl_Check(ptr) + convert(T, PyJl_GetValue(ptr))::T + else + pyconvert(T, pynew(incref(ptr)))::T + end +end + +function _getstr(ptr::C.PyPtr, what::String="argument") + o = pynew(incref(ptr)) + if pyisstr(o) + v = pystr_asstring(o) + unsafe_pydel!(o) + v + else + errset(pybuiltins.TypeError, "$what must be string, not '$(pytype(o).__name__)'") + unsafe_pydel!(o) + nothing + end +end + +function _gettype(ptr::C.PyPtr, what::String="type argument") + t = _getany(ptr) + if t isa Type + t + else + errset(pybuiltins.TypeError, "$what must be a Julia 'Type', not '$(typeof(t))'") + nothing + end +end + +function _pyjl_init(xptr::C.PyPtr, argsptr::C.PyPtr, kwargsptr::C.PyPtr) + if kwargsptr != C.PyNULL && C.PyDict_Size(kwargsptr) != 0 + errset(pybuiltins.TypeError, "keyword arguments not allowed") + return Cint(-1) + end + if argsptr == C.PyNULL + return Cint(0) + end + nargs = C.PyTuple_Size(argsptr) + if nargs == 0 + return Cint(0) + elseif nargs > 2 + errset(pybuiltins.TypeError, "Jl.__init__() takes up to 2 arguments ($nargs given)") + return Cint(-1) + end + vptr = C.PyTuple_GetItem(argsptr, 0) + try + if nargs == 1 + v = _getany(vptr) + else + tptr = C.PyTuple_GetItem(argsptr, 1) + t = _gettype(tptr, "type argument") + if t === nothing + return Cint(-1) + end + v = _getany(t, vptr) + end + PyJl_SetValue(xptr, v) + Cint(0) + catch exc + _raise(exc) + Cint(-1) + end +end + +function _pyjl_repr(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + buf = IOBuffer() + io = IOContext(buf, :limit=>true, :displaysize=>(23, 80)) + show(io, MIME("text/plain"), x) + str = String(take!(buf)) + sep = '\n' in str ? '\n' : ' ' + ans = pystr("Julia:$sep$str") + _return(ans, del=true) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_str(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + buf = IOBuffer() + io = IOContext(buf, :limit=>true, :displaysize=>(23, 80)) + print(io, x) + str = String(take!(buf)) + ans = pystr(str) + _return(ans, del=true) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_hash(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + mod(hash(x), C.Py_hash_t)::C.Py_hash_t + catch exc + _raise(exc) + C.Py_hash_t(0) + end +end + +_pyjl_attr_py2jl(k::String) = replace(k, r"_[b]+$" => (x -> "!"^(length(x) - 1))) + +_pyjl_attr_jl2py(k::String) = replace(k, r"!+$" => (x -> "_" * "b"^length(x))) + +function _pyjl_getattr(xptr::C.PyPtr, kptr::C.PyPtr) + infunc = false + x = nothing + k = :_ + try + # first do the generic lookup + vptr = C.PyObject_GenericGetAttr(xptr, kptr) + if vptr != C.PyNULL || !errmatches(pybuiltins.AttributeError) + return vptr + end + errclear() + # get the attribute name + kstr = _getstr(kptr, "attribute name") + if kstr === nothing + return C.PyNULL + end + # skip attributes starting with "__" or "jl_" + if startswith(kstr, "__") || startswith(kstr, "jl_") + C.PyErr_SetObject(pybuiltins.AttributeError, kptr) + return C.PyNULL + end + # get the property + x = PyJl_GetValue(xptr) + k = Symbol(_pyjl_attr_py2jl(kstr)) + infunc = true + v = getproperty(x, k) + infunc = false + JlC.PyJl_New(v) + catch exc + if infunc && !hasproperty(x, k) + C.PyErr_SetObject(pybuiltins.AttributeError, kptr) + else + _raise(exc) + end + C.PyNULL + end +end + +function _pyjl_setattr(xptr::C.PyPtr, kptr::C.PyPtr, vptr::C.PyPtr) + infunc = false + x = nothing + k = :_ + try + # first do the generic lookup + err = C.PyObject_GenericSetAttr(xptr, kptr, vptr) + if iszero(err) || !errmatches(pybuiltins.AttributeError) + return err + end + errclear() + # if deleting, raise an error + if vptr == C.PyNULL + errset(pybuiltins.TypeError, "Julia objects do not support deleting attributes") + return Cint(-1) + end + # get the attribute name + kstr = _getstr(kptr, "attribute name") + if kstr === nothing + return Cint(-1) + end + # skip attributes starting with "__" or "jl_" + if startswith(kstr, "__") || startswith(kstr, "jl_") + C.PyErr_SetObject(pybuiltins.AttributeError, kptr) + return Cint(-1) + end + # set the property + x = PyJl_GetValue(xptr) + k = Symbol(_pyjl_attr_py2jl(kstr)) + v = _getany(vptr) + infunc = true + setproperty!(x, k, v) + infunc = false + Cint(0) + catch exc + if infunc && !hasproperty(x, k) + C.PyErr_SetObject(pybuiltins.AttributeError, kptr) + else + _raise(exc) + end + Cint(-1) + end +end + +function _pyjl_dir(xptr::C.PyPtr, ::C.PyPtr) + try + x = PyJl_GetValue(xptr) + ks = Symbol[] + if x isa Module + append!(ks, names(x, all = true, imported = true)) + for m in ccall(:jl_module_usings, Any, (Any,), x)::Vector + append!(ks, names(m)) + end + else + for k in propertynames(x) + if k isa Symbol + push!(ks, k) + end + end + end + ks = map(string, ks) + filter!(k->!startswith(k, "#"), ks) + ks = map(_pyjl_attr_jl2py, ks) + v = pylist(ks) + v.extend(pybuiltins.object.__dir__(pynew(incref(xptr)))) + _return(v, del=true) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_len(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + v = length(x) + convert(C.Py_ssize_t, v) + catch exc + _raise(exc) + C.Py_ssize_t(-1) + end +end + +function _pyjl_getitem(xptr::C.PyPtr, kptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + if C.PyTuple_Check(kptr) + k = pyconvert(Vector{Any}, pynew(incref(kptr))) + if x isa Type + v = x{k...} + else + v = x[k...] + end + else + k = _getany(kptr) + if x isa Type + v = x{k} + else + v = x[k] + end + end + PyJl_New(v) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_setitem(xptr::C.PyPtr, kptr::C.PyPtr, vptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + if vptr == C.PyNULL + k = _getany(kptr) + if x isa AbstractVector + deleteat!(x, k) + else + delete!(x, k) + end + else + v = _getany(vptr) + if C.PyTuple_Check(kptr) + k = pyconvert(Vector{Any}, pynew(incref(vptr))) + x[k...] = v + else + k = _getany(kptr) + x[k] = v + end + end + Cint(0) + catch exc + _raise(exc) + Cint(-1) + end +end + +struct _pyjl_unary_op{F} + op::F +end +function (f::_pyjl_unary_op)(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + v = f.op(x) + _return(pyjl(v), del=true) + catch exc + _raise(exc) + C.PyNULL + end +end + +struct _pyjl_binary_op{F} + op::F +end +function (f::_pyjl_binary_op)(xptr::C.PyPtr, yptr::C.PyPtr) + try + x = _getany(xptr) + y = _getany(yptr) + v = f.op(x, y) + _return(pyjl(v), del=true) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_power(xptr::C.PyPtr, yptr::C.PyPtr, zptr::C.PyPtr) + try + x = _getany(xptr) + y = _getany(yptr) + if zptr == C.PyNULL || zptr == C.POINTERS._Py_NoneStruct + v = x^y + else + z = _getany(zptr) + v = powermod(x, y, z) + end + PyJl_New(v) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_bool(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + if x isa Bool + Cint(x) + else + errset(pybuiltins.TypeError, "only Julia 'Bool' values can be checked for truthyness, not '$(typeof(x))'") + Cint(-1) + end + catch exc + _raise(exc) + Cint(-1) + end +end + +function _pyjl_int(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + _return(pyint(convert(Integer, x)), del=true) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_index(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + if x isa Integer + _return(pyint(x), del=true) + else + errset(pybuiltins.TypeError, "only Julia 'Integer' values can be used for indexing, not '$(typeof(x))'") + C.PyNULL + end + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_float(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + _return(pyfloat(convert(Cdouble, x)), del=true) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_contains(xptr::C.PyPtr, vptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + v = _getany(vptr) + Cint((v in x)::Bool) + catch exc + _raise(exc) + Cint(-1) + end +end + +function _pyjl_call(xptr::C.PyPtr, argsptr::C.PyPtr, kwargsptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + if argsptr == C.PyNULL + args = nothing + else + argsobj = pynew(incref(argsptr)) + # TODO: avoid pyconvert, since we know args must be a tuple? + args = pyconvert(Vector{Any}, argsobj) + unsafe_pydel!(argsobj) + if isempty(args) + args = nothing + end + end + if kwargsptr == C.PyNULL + kwargs = nothing + else + kwargsobj = pynew(incref(kwargsptr)) + # TODO: avoid pyconvert, since we know kwargs must be a dict? + kwargs = pyconvert(Dict{Symbol,Any}, kwargsobj) + unsafe_pydel!(kwargsobj) + if isempty(kwargs) + kwargs = nothing + end + end + if kwargs !== nothing + if args !== nothing + v = x(args...; kwargs...) + else + v = x(; kwargs...) + end + else + if args !== nothing + v = x(args...) + else + v = x() + end + end + PyJl_New(v) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_richcompare(xptr::C.PyPtr, yptr::C.PyPtr, op::Cint) + try + x = PyJl_GetValue(xptr) + y = _getany(yptr) + if op == C.Py_EQ + v = (x == y) + elseif op == C.Py_NE + v = (x != y) + elseif op == C.Py_LT + v = (x < y) + elseif op == C.Py_GT + v = (x > y) + elseif op == C.Py_LE + v = (x <= y) + elseif op == C.Py_GE + v = (x >= y) + else + error("invalid rich comparison operator: $op") + end + PyJl_New(v) + catch exc + _raise(exc) + C.PyNULL + end +end + +pyjl_enter(xval, x::Py) = error("Julia '$(typeof(xval))' object does not support the context manager protocol") +pyjl_enter(xval::Base.AbstractLock, x::Py) = (lock(x); x) +pyjl_enter(xval::IO, x::Py) = x + +pyjl_exit(xval::Base.AbstractLock, x::Py, et::Py, ev::Py, eb::Py) = (unlock(xval); false) +pyjl_exit(xval::IO, x::Py, et::Py, ev::Py, eb::Py) = (close(xval); false) + +function _pyjl_enter(xptr::C.PyPtr, ::C.PyPtr) + try + xval = PyJl_GetValue(xptr) + x = pynew(incref(xptr)) + v = pyjl_enter(xval, x) + _return(v, :any) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_exit(xptr::C.PyPtr, args::C.PyPtr) + try + C.PyTuple_Size(args) == 3 || error("expecting 3 arguments to __exit__") + xval = PyJl_GetValue(xptr) + etptr = C.PyTuple_GetItem(args, 0) + evptr = C.PyTuple_GetItem(args, 1) + ebptr = C.PyTuple_GetItem(args, 2) + x = pynew(incref(xptr)) + et = pynew(incref(etptr)) + ev = pynew(incref(evptr)) + eb = pynew(incref(ebptr)) + v = pyjl_exit(xval, x, et, ev, eb)::Bool + _return(v, :bool) + catch exc + _raise(exc) + C.PyNULL + end +end + +struct _pyjl_generic_method{T,N,F} + func::F +end +_pyjl_generic_method{T,N}(func::F) where {T,N,F} = _pyjl_generic_method{T,N,F}(func) +_pyjl_generic_method{T,N}(func::F) where {T,N,F<:Type} = _pyjl_generic_method{T,N,Type{func}}(func) + +function (m::_pyjl_generic_method{T,0})(xptr::C.PyPtr, ::C.PyPtr) where {T} + try + x = PyJl_GetValue(xptr) + v = m.func(x) + _return(v, T) + catch exc + _raise(exc) + C.PyNULL + end +end + +function (m::_pyjl_generic_method{T,1})(xptr::C.PyPtr, yptr::C.PyPtr) where {T} + try + x = PyJl_GetValue(xptr) + y = _getany(yptr) + v = m.func(x, y) + _return(v, T) + catch exc + _raise(exc) + C.PyNULL + end +end + +const _pyjl_isnothing = _pyjl_generic_method{:bool,0}(isnothing) +const _pyjl_ismissing = _pyjl_generic_method{:bool,0}(ismissing) +const _pyjl_is = _pyjl_generic_method{:bool,1}(===) +const _pyjl_isequal = _pyjl_generic_method{:bool,1}(isequal) +const _pyjl_isless = _pyjl_generic_method{:bool,1}(isless) +const _pyjl_isa = _pyjl_generic_method{:bool,1}(isa) +const _pyjl_issubtype = _pyjl_generic_method{:issubtype,1}(<:) +const _pyjl_to_py = _pyjl_generic_method{:any,0}(identity) +const _pyjl_typeof = _pyjl_generic_method{:jl,0}(typeof) +const _pyjl_complex = _pyjl_generic_method{:complex,0}(Complex{Cdouble}) +const _pyjl_doc = _pyjl_generic_method{:jl,0}(Docs.doc) + +mutable struct Iterator + value::Any + state::Any + started::Bool + finished::Bool +end + +Iterator(x) = Iterator(x, nothing, false, false) +Iterator(x::Iterator) = x + +function Base.iterate(x::Iterator, ::Nothing=nothing) + if x.finished + s = nothing + elseif x.started + s = iterate(x.value, x.state) + else + s = iterate(x.value) + end + if s === nothing + x.finished = true + nothing + else + x.started = true + x.state = s[2] + (s[1], nothing) + end +end + +function _pyjl_iter(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + v = Iterator(x) + PyJl_New(v) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_iternext(xptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + s = iterate(x) + if s === nothing + C.PyErr_SetNone(C.POINTERS.PyExc_StopIteration) + C.PyNULL + else + v = (s::Tuple{Any,Any})[1] + PyJl_New(v) + end + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_reversed(xptr::C.PyPtr, ::C.PyPtr) + try + # same as _pyjl_iter but on the reversed iterator + x = PyJl_GetValue(xptr) + v = Iterator(Base.Iterators.reverse(x)) + PyJl_New(v) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_convert(xptr::C.PyPtr, tptr::C.PyPtr) + try + x = PyJl_GetValue(xptr) + t = _gettype(tptr) + if t === nothing + return C.PyNULL + end + v = convert(t, x)::t + PyJl_New(v) + catch exc + _raise(exc) + C.PyNULL + end +end + +function _pyjl_eval(mptr::C.PyPtr, xptr::C.PyPtr) + try + m = PyJl_GetValue(mptr) + if !(m isa Module) + errset(pybuiltins.TypeError, "can only call jl_eval on a Julia 'Module', not a '$(typeof(m))'") + return C.PyNULL + end + xo = pynew(incref(xptr)) + if pyisstr(xo) + # python strings are parsed into Julia expressions + x = pystr_asstring(xo) + unsafe_pydel!(xo) + ex = Meta.parseall(strip(x)) + elseif PyJl_Check(xptr) + # Julia values are assumed to be expressions already + ex = _getany(xo) + unsafe_pydel!(xo) + else + errset(pybuiltins.TypeError, "argument to jl_eval must be string or Jl, not '$(pytype(xo).__name__)'") + end + v = Base.eval(m, ex) + PyJl_New(v) + catch exc + _raise(exc) + C.PyNULL + end +end + +const PYJLBUFCACHE = Dict{Ptr{Cvoid},Any}() + +@kwdef struct PyBufferInfo{N} + # data + ptr::Ptr{Cvoid} + readonly::Bool + # items + itemsize::Int + format::String + # layout + shape::NTuple{N,Int} + strides::NTuple{N,Int} + suboffsets::NTuple{N,Int} = ntuple(i -> -1, N) +end + +PyBufferInfo(::Any) = nothing + +# TODO: the full implementation +PyBufferInfo(x::Array{Cdouble,N}) where {N} = PyBufferInfo{N}(ptr=Ptr{Cvoid}(pointer(x)), readonly=false, itemsize=sizeof(Cdouble), format="d", shape=size(x), strides=strides(x) .* sizeof(Cdouble)) + +function _pyjl_get_buffer_impl(obj::C.PyPtr, buf::Ptr{C.Py_buffer}, flags::Cint, info::PyBufferInfo{N}) where {N} + b = UnsafePtr(buf) + c = [] + + # not influenced by flags: obj, buf, len, itemsize, ndim + b.obj[] = C_NULL + b.buf[] = info.ptr + b.itemsize[] = info.itemsize + b.len[] = info.itemsize * prod(info.shape) + b.ndim[] = N + + # readonly + if !info.readonly + b.readonly[] = 0 + elseif Utils.isflagset(flags, C.PyBUF_WRITABLE) + C.PyErr_SetString(C.POINTERS.PyExc_BufferError, "not writable") + return Cint(-1) + else + b.readonly[] = 1 + end + + # format + if Utils.isflagset(flags, C.PyBUF_FORMAT) + push!(c, info.format) + b.format[] = pointer(info.format) + else + b.format[] = C_NULL + end + + # shape + if Utils.isflagset(flags, C.PyBUF_ND) + shape = C.Py_ssize_t[info.shape...] + push!(c, shape) + b.shape[] = pointer(shape) + else + b.shape[] = C_NULL + end + + # strides + if Utils.isflagset(flags, C.PyBUF_STRIDES) + strides = C.Py_ssize_t[info.strides...] + push!(c, strides) + b.strides[] = pointer(strides) + elseif Utils.size_to_cstrides(info.itemsize, info.shape) == info.strides + b.strides[] = C_NULL + else + C.PyErr_SetString(C.POINTERS.PyExc_BufferError, "not C contiguous and strides not requested") + return Cint(-1) + end + + # suboffsets + if all(==(-1), info.suboffsets) + b.suboffsets[] = C_NULL + elseif Utils.isflagset(flags, C.PyBUF_INDIRECT) + suboffsets = C.Py_ssize_t[info.suboffsets...] + push!(c, suboffsets) + b.suboffsets[] = pointer(suboffsets) + else + C.PyErr_SetString(C.POINTERS.PyExc_BufferError, "indirect array and suboffsets not requested") + return Cint(-1) + end + + # check contiguity + if Utils.isflagset(flags, C.PyBUF_C_CONTIGUOUS) + if Utils.size_to_cstrides(info.itemsize, info.shape) != info.strides + C.PyErr_SetString(C.POINTERS.PyExc_BufferError, "not C contiguous") + return Cint(-1) + end + end + if Utils.isflagset(flags, C.PyBUF_F_CONTIGUOUS) + if Utils.size_to_fstrides(info.itemsize, info.shape) != info.strides + C.PyErr_SetString(C.POINTERS.PyExc_BufferError, "not Fortran contiguous") + return Cint(-1) + end + end + if Utils.isflagset(flags, C.PyBUF_ANY_CONTIGUOUS) + if Utils.size_to_cstrides(info.itemsize, info.shape) != info.strides && + Utils.size_to_fstrides(info.itemsize, info.shape) != info.strides + C.PyErr_SetString(C.POINTERS.PyExc_BufferError, "not contiguous") + return Cint(-1) + end + end + + # internal + cptr = Base.pointer_from_objref(c) + PYJLBUFCACHE[cptr] = c + b.internal[] = cptr + + # obj + C.Py_IncRef(obj) + b.obj[] = obj + Cint(0) +end + +function _pyjl_get_buffer(o::C.PyPtr, buf::Ptr{C.Py_buffer}, flags::Cint) + try + v = PyJl_GetValue(o) + info = PyBufferInfo(v) + if info === nothing + C.PyErr_SetString(C.POINTERS.PyExc_BufferError, "Julia '$(typeof(v))' does not support the buffer protocol") + return Cint(-1) + end + return _pyjl_get_buffer_impl(o, buf, flags, info::PyBufferInfo)::Cint + catch exc + @debug "error getting the buffer" + C.PyErr_SetString(C.POINTERS.PyExc_BufferError, "some error occurred getting the buffer") + return Cint(-1) + end +end + +function _pyjl_release_buffer(::C.PyPtr, buf::Ptr{C.Py_buffer}) + delete!(PYJLBUFCACHE, UnsafePtr(buf).internal[!]) + nothing +end + +# function _pyjl_reduce(self::C.PyPtr, ::C.PyPtr) +# v = _pyjl_serialize(self, C.PyNULL) +# v == C.PyNULL && return C.PyNULL +# args = C.PyTuple_New(1) +# args == C.PyNULL && (C.Py_DecRef(v); return C.PyNULL) +# err = C.PyTuple_SetItem(args, 0, v) +# err == -1 && (C.Py_DecRef(args); return C.PyNULL) +# red = C.PyTuple_New(2) +# red == C.PyNULL && (C.Py_DecRef(args); return C.PyNULL) +# err = C.PyTuple_SetItem(red, 1, args) +# err == -1 && (C.Py_DecRef(red); return C.PyNULL) +# f = C.PyObject_GetAttrString(self, "_jl_deserialize") +# f == C.PyNULL && (C.Py_DecRef(red); return C.PyNULL) +# err = C.PyTuple_SetItem(red, 0, f) +# err == -1 && (C.Py_DecRef(red); return C.PyNULL) +# return red +# end + +# function _pyjl_serialize(self::C.PyPtr, ::C.PyPtr) +# try +# io = IOBuffer() +# serialize(io, PyJl_GetValue(self)) +# b = take!(io) +# return C.PyBytes_FromStringAndSize(pointer(b), sizeof(b)) +# catch e +# C.PyErr_SetString(C.POINTERS.PyExc_Exception, "error serializing this value") +# # wrap sprint in another try-catch block to prevent this function from throwing +# try +# @debug "Caught exception $(sprint(showerror, e, catch_backtrace()))" +# catch +# end +# return C.PyNULL +# end +# end + +# function _pyjl_deserialize(t::C.PyPtr, v::C.PyPtr) +# try +# ptr = Ref{Ptr{Cchar}}() +# len = Ref{C.Py_ssize_t}() +# err = C.PyBytes_AsStringAndSize(v, ptr, len) +# err == -1 && return C.PyNULL +# io = IOBuffer(unsafe_wrap(Array, Ptr{UInt8}(ptr[]), Int(len[]))) +# x = deserialize(io) +# return PyJl_New(t, x) +# catch e +# C.PyErr_SetString(C.POINTERS.PyExc_Exception, "error deserializing this value") +# # wrap sprint in another try-catch block to prevent this function from throwing +# try +# @debug "Caught exception $(sprint(showerror, e, catch_backtrace()))" +# catch +# end +# return C.PyNULL +# end +# end + +const _pyjl_name = "juliacall.Jl" +const _pyjl_type = fill(C.PyTypeObject()) +const _pyjl_docstring = """ +A Julia object. + +Jl(value) creates a Julia object with the given 'value'. + +Jl(value, type) ensures the object has the given Julia 'type'. +""" +# const _pyjl_isnull_name = "_jl_isnull" +# const _pyjl_callmethod_name = "_jl_callmethod" +# const _pyjl_reduce_name = "__reduce__" +# const _pyjl_serialize_name = "_jl_serialize" +# const _pyjl_deserialize_name = "_jl_deserialize" +const _pyjl_to_py_name = "jl_to_py" +const _pyjl_eval_name = "jl_eval" +const _pyjl_typeof_name = "jl_typeof" +const _pyjl_convert_name = "jl_convert" +const _pyjl_isnothing_name = "jl_isnothing" +const _pyjl_ismissing_name = "jl_ismissing" +const _pyjl_is_name = "jl_is" +const _pyjl_isequal_name = "jl_isequal" +const _pyjl_isless_name = "jl_isless" +const _pyjl_isa_name = "jl_isa" +const _pyjl_issubtype_name = "jl_issubtype" +const _pyjl_doc_name = "jl_doc" +const _pyjl_dir_name = "__dir__" +const _pyjl_reversed_name = "__reversed__" +const _pyjl_complex_name = "__complex__" +const _pyjl_enter_name = "__enter__" +const _pyjl_exit_name = "__exit__" +const _pyjl_methods = Vector{C.PyMethodDef}() +const _pyjl_as_buffer = fill(C.PyBufferProcs()) +const _pyjl_as_number = fill(C.PyNumberMethods()) +const _pyjl_as_sequence = fill(C.PySequenceMethods()) +const _pyjl_as_mapping = fill(C.PyMappingMethods()) + +macro pyjl_method(func::Symbol, flags) + nargs = occursin("KWARGS", string(flags)) ? 3 : 2 + :(C.PyMethodDef( + name = pointer($(Symbol(func, :_name))), + meth = @cfunction($func, C.PyPtr, ($([:(C.PyPtr) for _ in 1:nargs]...),)), + flags = $(esc(flags)), + )) +end + +function init_pyjl() + empty!(_pyjl_methods) + push!(_pyjl_methods, + @pyjl_method(_pyjl_dir, C.Py_METH_NOARGS), + @pyjl_method(_pyjl_reversed, C.Py_METH_NOARGS), + @pyjl_method(_pyjl_complex, C.Py_METH_NOARGS), + @pyjl_method(_pyjl_enter, C.Py_METH_NOARGS), + @pyjl_method(_pyjl_exit, C.Py_METH_VARARGS), + @pyjl_method(_pyjl_to_py, C.Py_METH_NOARGS), + @pyjl_method(_pyjl_eval, C.Py_METH_O), + @pyjl_method(_pyjl_typeof, C.Py_METH_NOARGS), + @pyjl_method(_pyjl_convert, C.Py_METH_O), + @pyjl_method(_pyjl_isnothing, C.Py_METH_NOARGS), + @pyjl_method(_pyjl_ismissing, C.Py_METH_NOARGS), + @pyjl_method(_pyjl_is, C.Py_METH_O), + @pyjl_method(_pyjl_isequal, C.Py_METH_O), + @pyjl_method(_pyjl_isless, C.Py_METH_O), + @pyjl_method(_pyjl_isa, C.Py_METH_O), + @pyjl_method(_pyjl_issubtype, C.Py_METH_O), + @pyjl_method(_pyjl_doc, C.Py_METH_NOARGS), + # TODO: __reduce__ + # TODO: __array__ + # TODO: __array_interface__ + # TODO: __array_struct__ + # TODO: jl_serialize + # TODO: jl_deserialize + # TODO: jl_isinstance + # TODO: jl_issubtype + # TODO: jl_size + C.PyMethodDef(), + ) + _pyjl_as_number[] = C.PyNumberMethods( + add = @cfunction(_pyjl_binary_op(+), C.PyPtr, (C.PyPtr, C.PyPtr)), + subtract = @cfunction(_pyjl_binary_op(-), C.PyPtr, (C.PyPtr, C.PyPtr)), + multiply = @cfunction(_pyjl_binary_op(*), C.PyPtr, (C.PyPtr, C.PyPtr)), + remainder = @cfunction(_pyjl_binary_op(%), C.PyPtr, (C.PyPtr, C.PyPtr)), + lshift = @cfunction(_pyjl_binary_op(<<), C.PyPtr, (C.PyPtr, C.PyPtr)), + rshift = @cfunction(_pyjl_binary_op(>>), C.PyPtr, (C.PyPtr, C.PyPtr)), + and = @cfunction(_pyjl_binary_op(&), C.PyPtr, (C.PyPtr, C.PyPtr)), + xor = @cfunction(_pyjl_binary_op(⊻), C.PyPtr, (C.PyPtr, C.PyPtr)), + or = @cfunction(_pyjl_binary_op(|), C.PyPtr, (C.PyPtr, C.PyPtr)), + floordivide = @cfunction(_pyjl_binary_op(÷), C.PyPtr, (C.PyPtr, C.PyPtr)), + truedivide = @cfunction(_pyjl_binary_op(/), C.PyPtr, (C.PyPtr, C.PyPtr)), + negative = @cfunction(_pyjl_unary_op(-), C.PyPtr, (C.PyPtr,)), + positive = @cfunction(_pyjl_unary_op(+), C.PyPtr, (C.PyPtr,)), + absolute = @cfunction(_pyjl_unary_op(abs), C.PyPtr, (C.PyPtr,)), + invert = @cfunction(_pyjl_unary_op(-), C.PyPtr, (C.PyPtr,)), + bool = @cfunction(_pyjl_bool, Cint, (C.PyPtr,)), + int = @cfunction(_pyjl_int, C.PyPtr, (C.PyPtr,)), + index = @cfunction(_pyjl_index, C.PyPtr, (C.PyPtr,)), + float = @cfunction(_pyjl_float, C.PyPtr, (C.PyPtr,)), + power = @cfunction(_pyjl_power, C.PyPtr, (C.PyPtr, C.PyPtr, C.PyPtr)), + # TODO: matrixmultiply + # TODO: inplace_* + ) + _pyjl_as_sequence[] = C.PySequenceMethods( + # TODO: concat + # TODO: repeat + # TODO: inplace_concat + # TODO: inplace_repeat + contains = @cfunction(_pyjl_contains, Cint, (C.PyPtr, C.PyPtr)), + ) + _pyjl_as_mapping[] = C.PyMappingMethods( + length = @cfunction(_pyjl_len, C.Py_ssize_t, (C.PyPtr,)), + subscript = @cfunction(_pyjl_getitem, C.PyPtr, (C.PyPtr, C.PyPtr)), + ass_subscript = @cfunction(_pyjl_setitem, Cint, (C.PyPtr, C.PyPtr, C.PyPtr)), + ) + _pyjl_as_buffer[] = C.PyBufferProcs( + get = @cfunction(_pyjl_get_buffer, Cint, (C.PyPtr, Ptr{C.Py_buffer}, Cint)), + release = @cfunction(_pyjl_release_buffer, Cvoid, (C.PyPtr, Ptr{C.Py_buffer})), + ) + _pyjl_type[] = C.PyTypeObject( + name = pointer(_pyjl_name), + doc = pointer(_pyjl_docstring), + basicsize = sizeof(PyJuliaValueObject), + new = @cfunction(_pyjl_new, C.PyPtr, (C.PyPtr, C.PyPtr, C.PyPtr)), + init = @cfunction(_pyjl_init, Cint, (C.PyPtr, C.PyPtr, C.PyPtr)), + dealloc = @cfunction(_pyjl_dealloc, Cvoid, (C.PyPtr,)), + flags = C.Py_TPFLAGS_BASETYPE | C.Py_TPFLAGS_HAVE_VERSION_TAG, + weaklistoffset = fieldoffset(PyJuliaValueObject, 3), + getattro = @cfunction(_pyjl_getattr, C.PyPtr, (C.PyPtr, C.PyPtr)), + setattro = @cfunction(_pyjl_setattr, Cint, (C.PyPtr, C.PyPtr, C.PyPtr)), + methods = pointer(_pyjl_methods), + as_number = pointer(_pyjl_as_number), + as_sequence = pointer(_pyjl_as_sequence), + as_mapping = pointer(_pyjl_as_mapping), + as_buffer = pointer(_pyjl_as_buffer), + repr = @cfunction(_pyjl_repr, C.PyPtr, (C.PyPtr,)), + str = @cfunction(_pyjl_str, C.PyPtr, (C.PyPtr,)), + hash = @cfunction(_pyjl_hash, C.Py_hash_t, (C.PyPtr,)), + call = @cfunction(_pyjl_call, C.PyPtr, (C.PyPtr, C.PyPtr, C.PyPtr)), + richcompare = @cfunction(_pyjl_richcompare, C.PyPtr, (C.PyPtr, C.PyPtr, Cint)), + iter = @cfunction(_pyjl_iter, C.PyPtr, (C.PyPtr,)), + iternext = @cfunction(_pyjl_iternext, C.PyPtr, (C.PyPtr,)), + ) + o = PyJl_Type[] = C.PyPtr(pointer(_pyjl_type)) + if C.PyType_Ready(o) == -1 + C.PyErr_Print() + error("Error initializing 'juliacall.Jl'") + end +end + +function __init__() + C.with_gil() do + init_pyjl() + end +end + +PyJl_GetIndex(o) = UnsafePtr{PyJuliaValueObject}(C.ptr(o)).value[] + +PyJl_IsNew(o) = PyJl_GetIndex(o) == 0 + +function PyJl_GetValue(o) + idx = PyJl_GetIndex(o) + if idx == 0 + nothing + else + PYJLVALUES[idx] + end +end + +function PyJl_SetValue(o, @nospecialize(v)) + idx = PyJl_GetIndex(o) + if idx == 0 + if isempty(PYJLFREEVALUES) + push!(PYJLVALUES, v) + idx = length(PYJLVALUES) + else + idx = pop!(PYJLFREEVALUES) + PYJLVALUES[idx] = v + end + UnsafePtr{PyJuliaValueObject}(C.ptr(o)).value[] = idx + else + PYJLVALUES[idx] = v + end + nothing +end + +function PyJl_New() + C.PyObject_CallObject(PyJl_Type[], C.PyNULL) +end + +function PyJl_New(@nospecialize(v)) + o = PyJl_New() + o == C.PyNULL && return C.PyNULL + PyJl_SetValue(o, v) + o +end + +function PyJl_Check(o) + C.PyObject_IsInstance(o, PyJl_Type[]) == 1 +end + +end diff --git a/src/JlCore/JlCore.jl b/src/JlCore/JlCore.jl new file mode 100644 index 00000000..bcf8b1e4 --- /dev/null +++ b/src/JlCore/JlCore.jl @@ -0,0 +1,66 @@ +""" + module PythonCall.JlCore + +Implements the Python type `juliacall.Jl` for wrapping Julia values. Exports [`pyjl`](@ref). +""" +module JlCore + +using ..Core +using ..Core: Core, errcheck +using ..Convert: pyconvert_add_rule, pyconvert_tryconvert, PYCONVERT_PRIORITY_WRAP + +function pyjl end + +include("C.jl") + +""" + pyjl(v) + +Wrap the Julia value `v` as a Python `juliacall.Jl`. +""" +pyjl(@nospecialize(v)) = pynew(errcheck(JlC.PyJl_New(v))) +export pyjl + +""" + pyjlvalue(x) + +Extract the Julia value from the given Python `juliacall.Jl`. +""" +function pyjlvalue(x) + x = Py(x) + JlC.PyJl_Check(x) || throw(PyException(pybuiltins.TypeError("expecting a 'juliacall.Jl' but got a '$(pytype(x))'"))) + JlC.PyJl_GetValue(x) +end +export pyjlvalue + +""" + pyisjl(x) + +Test whether the given Python object is a `juliacall.Jl`. +""" +function pyisjl(x) + x = Py(x) + JlC.PyJl_Check(x) +end +export pyisjl + +# the fallback conversion +Core.Py(x) = pyjl(x) + +const pyjltype = pynew() + +pyconvert_rule_jl(::Type{T}, x::Py) where {T} = pyconvert_tryconvert(T, pyjlvalue(x)) + +function __init__() + JlC.C.with_gil() do + Core.unsafe_setptr!(pyjltype, JlC.PyJl_Type[]) + jl = Core.pyjuliacallmodule + jl.Jl = pyjltype + jl.Core = pyjl(Base.Core) + jl.Base = pyjl(Base) + jl.Main = pyjl(Main) + end + pyconvert_add_rule("juliacall:Jl", Any, pyconvert_rule_jl, PYCONVERT_PRIORITY_WRAP) +end + +end diff --git a/src/JlCore/api.jl b/src/JlCore/api.jl new file mode 100644 index 00000000..e69de29b diff --git a/src/JlCore/juliacall.jl b/src/JlCore/juliacall.jl new file mode 100644 index 00000000..e69de29b diff --git a/src/JlWrap/C.jl b/src/JlWrap/C.jl index e90dc478..287f0e04 100644 --- a/src/JlWrap/C.jl +++ b/src/JlWrap/C.jl @@ -321,12 +321,12 @@ function __init__() end end -PyJuliaValue_IsNull(o::C.PyPtr) = UnsafePtr{PyJuliaValueObject}(o).value[] == 0 +PyJuliaValue_IsNull(o) = UnsafePtr{PyJuliaValueObject}(C.ptr(o)).value[] == 0 -PyJuliaValue_GetValue(o::C.PyPtr) = PYJLVALUES[UnsafePtr{PyJuliaValueObject}(o).value[]] +PyJuliaValue_GetValue(o) = PYJLVALUES[UnsafePtr{PyJuliaValueObject}(C.ptr(o)).value[]] -PyJuliaValue_SetValue(o::C.PyPtr, @nospecialize(v)) = begin - idx = UnsafePtr{PyJuliaValueObject}(o).value[] +PyJuliaValue_SetValue(o, @nospecialize(v)) = begin + idx = UnsafePtr{PyJuliaValueObject}(C.ptr(o)).value[] if idx == 0 if isempty(PYJLFREEVALUES) push!(PYJLVALUES, v) @@ -342,7 +342,7 @@ PyJuliaValue_SetValue(o::C.PyPtr, @nospecialize(v)) = begin nothing end -PyJuliaValue_New(t::C.PyPtr, @nospecialize(v)) = begin +function PyJuliaValue_New(t, @nospecialize(v)) if C.PyType_IsSubtype(t, PyJuliaBase_Type[]) != 1 C.PyErr_SetString(C.POINTERS.PyExc_TypeError, "Expecting a subtype of 'juliacall.ValueBase'") return C.PyNULL diff --git a/src/JlWrap/JlWrap.jl b/src/JlWrap/JlWrap.jl index b794f366..bfb8efdb 100644 --- a/src/JlWrap/JlWrap.jl +++ b/src/JlWrap/JlWrap.jl @@ -7,7 +7,7 @@ module JlWrap using ..PythonCall: PythonCall using ..Core -using ..Core: C, Utils, pynew, @autopy, incref, decref, setptr!, getptr, pyjuliacallmodule, pycopy!, errcheck, errset, PyNULL, pyistuple, pyisnull, pyJuliaError, pydel!, pyistype, pytypecheck, pythrow, pytuple_getitem, pyisslice, pystr_asstring, pyosmodule, pyisstr +using ..Core: C, Utils, pynew, @autopy, incref, decref, unsafe_setptr!, unsafe_getptr, getptr, pyjuliacallmodule, pycopy!, errcheck, errset, PyNULL, pyistuple, pyisnew, pyJlError, unsafe_pydel!, pyistype, pytypecheck, pythrow, pytuple_getitem, pyisslice, pystr_asstring, pyosmodule, pyisstr using ..Convert: pyconvert, @pyconvert, PYCONVERT_PRIORITY_WRAP, pyconvert_add_rule, pyconvert_tryconvert, pyconvertarg, pyconvert_result using Pkg: Pkg diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 92a53a2b..0d1305df 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -13,14 +13,14 @@ pyjlany_str(self) = Py(sprint(print, self)) function pyjlany_getattr(self, k_::Py) k = Symbol(pyjl_attr_py2jl(pyconvert(String, k_))) - pydel!(k_) + unsafe_pydel!(k_) Py(getproperty(self, k)) end pyjl_handle_error_type(::typeof(pyjlany_getattr), self, exc) = pybuiltins.AttributeError function pyjlany_setattr(self, k_::Py, v_::Py) k = Symbol(pyjl_attr_py2jl(pyconvert(String, k_))) - pydel!(k_) + unsafe_pydel!(k_) v = pyconvert(Any, v_) setproperty!(self, k, v) Py(nothing) @@ -40,8 +40,8 @@ function pyjlany_call(self, args_::Py, kwargs_::Py) else ans = Py(self()) end - pydel!(args_) - pydel!(kwargs_) + unsafe_pydel!(args_) + unsafe_pydel!(kwargs_) ans end pyjl_handle_error_type(::typeof(pyjlany_call), self, exc) = exc isa MethodError && exc.f === self ? pybuiltins.TypeError : PyNULL @@ -49,7 +49,7 @@ pyjl_handle_error_type(::typeof(pyjlany_call), self, exc) = exc isa MethodError function pyjlany_getitem(self, k_::Py) if pyistuple(k_) k = pyconvert(Vector{Any}, k_) - pydel!(k_) + unsafe_pydel!(k_) Py(self[k...]) else k = pyconvert(Any, k_) @@ -62,7 +62,7 @@ function pyjlany_setitem(self, k_::Py, v_::Py) v = pyconvert(Any, v_) if pyistuple(k_) k = pyconvert(Vector{Any}, k_) - pydel!(k_) + unsafe_pydel!(k_) self[k...] = v else k = pyconvert(Any, k_) @@ -75,7 +75,7 @@ pyjl_handle_error_type(::typeof(pyjlany_setitem), self, exc) = exc isa BoundsErr function pyjlany_delitem(self, k_::Py) if pyistuple(k_) k = pyconvert(Vector{Any}, k_) - pydel!(k_) + unsafe_pydel!(k_) delete!(self, k...) else k = pyconvert(Any, k_) @@ -95,7 +95,7 @@ end function (op::pyjlany_op)(self, other_::Py) if pyisjl(other_) other = pyjlvalue(other_) - pydel!(other_) + unsafe_pydel!(other_) Py(op.op(self, other)) else pybuiltins.NotImplemented @@ -105,8 +105,8 @@ function (op::pyjlany_op)(self, other_::Py, other2_::Py) if pyisjl(other_) && pyisjl(other2_) other = pyjlvalue(other_) other2 = pyjlvalue(other2_) - pydel!(other_) - pydel!(other2_) + unsafe_pydel!(other_) + unsafe_pydel!(other2_) Py(op.op(self, other, other2)) else pybuiltins.NotImplemented @@ -120,7 +120,7 @@ end function (op::pyjlany_rev_op)(self, other_::Py) if pyisjl(other_) other = pyjlvalue(other_) - pydel!(other_) + unsafe_pydel!(other_) Py(op.op(other, self)) else pybuiltins.NotImplemented @@ -130,8 +130,8 @@ function (op::pyjlany_rev_op)(self, other_::Py, other2_::Py) if pyisjl(other_) && pyisjl(other2_) other = pyjlvalue(other_) other2 = pyjlvalue(other2_) - pydel!(other_) - pydel!(other2_) + unsafe_pydel!(other_) + unsafe_pydel!(other2_) Py(op.op(other, self, other2)) else pybuiltins.NotImplemented @@ -179,7 +179,7 @@ function pyjlany_mimebundle(self, include::Py, exclude::Py) show(IOContext(io, :limit=>true), MIME(m), self) v = take!(io) ans[m] = vo = istextmime(m) ? pystr(String(v)) : pybytes(v) - pydel!(vo) + unsafe_pydel!(vo) catch err # silently skip anything that didn't work end diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index 2cb33bbc..e17ed604 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -65,7 +65,7 @@ function pyjl_getarrayindices(x::AbstractArray{T,N}, ks::Py) where {T,N} return ntuple(N) do i k = pytuple_getitem(ks, i-1) ans = pyjl_getaxisindex(axes(x, i), k) - pydel!(k) + unsafe_pydel!(k) return ans end else @@ -81,7 +81,7 @@ end function pyjlarray_getitem(x::AbstractArray{T,N}, k_::Py) where {T,N} k = pyjl_getarrayindices(x, k_) - pydel!(k_) + unsafe_pydel!(k_) if k isa NTuple{N,Int} return Py(x[k...]) else @@ -91,7 +91,7 @@ end function pyjlarray_setitem(x::AbstractArray{T,N}, k_::Py, v_::Py) where {T,N} k = pyjl_getarrayindices(x, k_) - pydel!(k_) + unsafe_pydel!(k_) if k isa NTuple{N,Int} v = pyconvertarg(T, v_, "value") x[k...] = v @@ -105,7 +105,7 @@ end function pyjlarray_delitem(x::AbstractArray{T,N}, k_::Py) where {T,N} if N == 1 k = pyjl_getarrayindices(x, k_) - pydel!(k_) + unsafe_pydel!(k_) deleteat!(x, k...) else errset(pybuiltins.TypeError, "can only delete from 1D arrays") @@ -117,7 +117,7 @@ pyjl_handle_error_type(::typeof(pyjlarray_delitem), x, exc::MethodError) = exc.f function pyjlarray_reshape(x::AbstractArray, shape_::Py) shape = pyconvertarg(Union{Int,Vector{Int}}, shape_, "shape") - pydel!(shape_) + unsafe_pydel!(shape_) return Py(reshape(x, shape...)) end @@ -254,7 +254,7 @@ pytypestrdescr(::Type{T}) where {T} = get!(PYTYPESTRDESCR, T) do isempty(ts) && return ("", PyNULL) push!( flds, - (nm isa Integer ? "f$(nm-1)" : string(nm), pyisnull(ds) ? ts : ds), + (nm isa Integer ? "f$(nm-1)" : string(nm), pyisnew(ds) ? ts : ds), ) d = (i == n ? sizeof(T) : fieldoffset(T, i + 1)) - (fieldoffset(T, i) + sizeof(tp)) @assert d ≥ 0 @@ -280,7 +280,7 @@ function pyjlarray_array_interface(x::AbstractArray{T,N}) where {T,N} d["data"] = data d["strides"] = strides(x) .* Base.aligned_sizeof(T) d["version"] = 3 - if !pyisnull(descr) + if !pyisnew(descr) d["descr"] = descr end return d diff --git a/src/JlWrap/base.jl b/src/JlWrap/base.jl index 08793e63..f595cca8 100644 --- a/src/JlWrap/base.jl +++ b/src/JlWrap/base.jl @@ -1,10 +1,10 @@ const pyjlbasetype = pynew() -_pyjl_getvalue(x) = @autopy x Cjl.PyJuliaValue_GetValue(getptr(x_)) +_pyjl_getvalue(x) = @autopy x Cjl.PyJuliaValue_GetValue(x_) -_pyjl_setvalue!(x, v) = @autopy x Cjl.PyJuliaValue_SetValue(getptr(x_), v) +_pyjl_setvalue!(x, v) = @autopy x Cjl.PyJuliaValue_SetValue(x_, v) -pyjl(t, v) = pynew(errcheck(@autopy t Cjl.PyJuliaValue_New(getptr(t_), v))) +pyjl(t, v) = pynew(errcheck(@autopy t Cjl.PyJuliaValue_New(t_, v))) """ pyisjl(x) @@ -16,7 +16,7 @@ export pyisjl pyjlisnull(x) = @autopy x begin if pyisjl(x_) - Cjl.PyJuliaValue_IsNull(getptr(x_)) + Cjl.PyJuliaValue_IsNull(x_) else error("Expecting a 'juliacall.ValueBase', got a '$(pytype(x_).__name__)'") end @@ -37,7 +37,7 @@ end export pyjlvalue function init_base() - setptr!(pyjlbasetype, incref(Cjl.PyJuliaBase_Type[])) + unsafe_setptr!(pyjlbasetype, incref(Cjl.PyJuliaBase_Type[])) pyjuliacallmodule.ValueBase = pyjlbasetype # conversion rule @@ -80,22 +80,23 @@ function Cjl._pyjl_callmethod(f, self_::C.PyPtr, args_::C.PyPtr, nargs::C.Py_ssi in_f = false else errset(pybuiltins.NotImplementedError, "__jl_callmethod not implemented for this many arguments") + return C.PyNULL end - return incref(getptr(ans)) + return incref(unsafe_getptr(ans)) # unsafe_getptr to allow errors to be set by returning PyNULL catch exc if exc isa PyException - Base.GC.@preserve exc C.PyErr_Restore(incref(getptr(exc._t)), incref(getptr(exc._v)), incref(getptr(exc._b))) + Base.GC.@preserve exc C.PyErr_Restore(incref(unsafe_getptr(exc._t)), incref(unsafe_getptr(exc._v)), incref(unsafe_getptr(exc._b))) return C.PyNULL else try if in_f return pyjl_handle_error(f, self, exc) else - errset(pyJuliaError, pytuple((pyjlraw(exc), pyjlraw(catch_backtrace())))) + errset(pyJlError, pytuple((pyjlraw(exc), pyjlraw(catch_backtrace())))) return C.PyNULL end catch - errset(pyJuliaError, "an error occurred while setting an error") + errset(pyJlError, "an error occurred while setting an error") return C.PyNULL end end @@ -105,9 +106,9 @@ end function pyjl_handle_error(f, self, exc) @nospecialize f self exc t = pyjl_handle_error_type(f, self, exc)::Py - if pyisnull(t) - # NULL => raise JuliaError - errset(pyJuliaError, pytuple((pyjlraw(exc), pyjlraw(catch_backtrace())))) + if pyisnew(t) + # NULL => raise JlError + errset(pyJlError, pytuple((pyjlraw(exc), pyjlraw(catch_backtrace())))) return C.PyNULL elseif pyistype(t) # Exception type => raise this type of error @@ -115,7 +116,7 @@ function pyjl_handle_error(f, self, exc) return C.PyNULL else # Otherwise, return the given object (e.g. NotImplemented) - return Base.GC.@preserve t incref(getptr(t)) + return Base.GC.@preserve t incref(unsafe_getptr(t)) end end diff --git a/src/JlWrap/callback.jl b/src/JlWrap/callback.jl index c25c10dd..4001b5f0 100644 --- a/src/JlWrap/callback.jl +++ b/src/JlWrap/callback.jl @@ -29,8 +29,8 @@ function pyjlcallback_call(self, args_::Py, kwargs_::Py) else ans = Py(self()) end - pydel!(args_) - pydel!(kwargs_) + unsafe_pydel!(args_) + unsafe_pydel!(kwargs_) ans end pyjl_handle_error_type(::typeof(pyjlcallback_call), self, exc::MethodError) = exc.f === self ? pybuiltins.TypeError : PyNULL diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index 85785fee..19ca2e77 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -84,20 +84,20 @@ function pyjlbinaryio_readinto(io::IO, b::Py) m = pybuiltins.memoryview(b) c = m.c_contiguous if !pytruth(c) - pydel!(c) + unsafe_pydel!(c) errset(pybuiltins.ValueError, "input buffer is not contiguous") return PyNULL end - pydel!(c) - buf = unsafe_load(C.PyMemoryView_GET_BUFFER(getptr(m))) + unsafe_pydel!(c) + buf = unsafe_load(C.PyMemoryView_GET_BUFFER(m)) if buf.readonly != 0 - pydel!(m) + unsafe_pydel!(m) errset(pybuiltins.ValueError, "output buffer is read-only") return PyNULL end data = unsafe_wrap(Array, Ptr{UInt8}(buf.buf), buf.len) nb = readbytes!(io, data) - pydel!(m) + unsafe_pydel!(m) return Py(nb) end pyjl_handle_error_type(::typeof(pyjlbinaryio_readinto), io, exc) = exc isa MethodError && exc.f === readbytes! ? pybuiltins.ValueError : PyNULL @@ -106,15 +106,15 @@ function pyjlbinaryio_write(io::IO, b::Py) m = pybuiltins.memoryview(b) c = m.c_contiguous if !pytruth(c) - pydel!(c) + unsafe_pydel!(c) errset(pybuiltins.ValueError, "input buffer is not contiguous") return PyNULL end - pydel!(c) - buf = unsafe_load(C.PyMemoryView_GET_BUFFER(getptr(m))) + unsafe_pydel!(c) + buf = unsafe_load(C.PyMemoryView_GET_BUFFER(m)) data = unsafe_wrap(Array, Ptr{UInt8}(buf.buf), buf.len) write(io, data) - pydel!(m) + unsafe_pydel!(m) return Py(buf.len) end pyjl_handle_error_type(::typeof(pyjlbinaryio_write), io, exc) = exc isa MethodError && exc.f === write ? pybuiltins.ValueError : PyNULL @@ -175,7 +175,7 @@ function pyjltextio_write(io::IO, s_::Py) # get the line separator linesep_ = pyosmodule.linesep linesep = pystr_asstring(linesep_) - pydel!(linesep_) + unsafe_pydel!(linesep_) # write the string # translating '\n' to os.linesep i = firstindex(s) diff --git a/src/JlWrap/number.jl b/src/JlWrap/number.jl index 1ed4013c..5b9b74e7 100644 --- a/src/JlWrap/number.jl +++ b/src/JlWrap/number.jl @@ -11,7 +11,7 @@ end function (op::pyjlnumber_op)(self, other_::Py) if pyisjl(other_) other = pyjlvalue(other_) - pydel!(other_) + unsafe_pydel!(other_) else other = @pyconvert(Number, other_, return pybuiltins.NotImplemented) end @@ -20,13 +20,13 @@ end function (op::pyjlnumber_op)(self, other_::Py, other2_::Py) if pyisjl(other_) other = pyjlvalue(other_) - pydel!(other_) + unsafe_pydel!(other_) else other = @pyconvert(Number, other_, return pybuiltins.NotImplemented) end if pyisjl(other2_) other2 = pyjlvalue(other2_) - pydel!(other2_) + unsafe_pydel!(other2_) else other2 = @pyconvert(Number, other2_, return pybuiltins.NotImplemented) end @@ -40,7 +40,7 @@ end function (op::pyjlnumber_rev_op)(self, other_::Py) if pyisjl(other_) other = pyjlvalue(other_) - pydel!(other_) + unsafe_pydel!(other_) else other = @pyconvert(Number, other_, return pybuiltins.NotImplemented) end @@ -49,13 +49,13 @@ end function (op::pyjlnumber_rev_op)(self, other_::Py, other2_::Py) if pyisjl(other_) other = pyjlvalue(other_) - pydel!(other_) + unsafe_pydel!(other_) else other = @pyconvert(Number, other_, return pybuiltins.NotImplemented) end if pyisjl(other2_) other2 = pyjlvalue(other2_) - pydel!(other2_) + unsafe_pydel!(other2_) else other2 = @pyconvert(Number, other2_, return pybuiltins.NotImplemented) end @@ -74,7 +74,7 @@ pyjl_handle_error_type(::typeof(pyjlreal_ceil), self, exc::MethodError) = exc.f function pyjlreal_round(self::Real, ndigits_::Py) ndigits = pyconvertarg(Union{Int,Nothing}, ndigits_, "ndigits") - pydel!(ndigits_) + unsafe_pydel!(ndigits_) if ndigits === nothing Py(round(Integer, self)) else diff --git a/src/JlWrap/raw.jl b/src/JlWrap/raw.jl index a531df37..6d77a44f 100644 --- a/src/JlWrap/raw.jl +++ b/src/JlWrap/raw.jl @@ -10,13 +10,13 @@ pyjl_attr_jl2py(k::String) = replace(k, r"!+$" => (x -> "_" * "b"^length(x))) function pyjlraw_getattr(self, k_::Py) k = Symbol(pyjl_attr_py2jl(pyconvert(String, k_))) - pydel!(k_) + unsafe_pydel!(k_) pyjlraw(getproperty(self, k)) end function pyjlraw_setattr(self, k_::Py, v_::Py) k = Symbol(pyjl_attr_py2jl(pyconvert(String, k_))) - pydel!(k_) + unsafe_pydel!(k_) v = pyconvert(Any, v_) setproperty!(self, k, v) Py(nothing) @@ -35,8 +35,8 @@ function pyjlraw_call(self, args_::Py, kwargs_::Py) else ans = pyjlraw(self()) end - pydel!(args_) - pydel!(kwargs_) + unsafe_pydel!(args_) + unsafe_pydel!(kwargs_) ans end @@ -45,7 +45,7 @@ pyjlraw_len(self) = Py(length(self)) function pyjlraw_getitem(self, k_::Py) if pyistuple(k_) k = pyconvert(Vector{Any}, k_) - pydel!(k_) + unsafe_pydel!(k_) pyjlraw(self[k...]) else k = pyconvert(Any, k_) @@ -57,7 +57,7 @@ function pyjlraw_setitem(self, k_::Py, v_::Py) v = pyconvert(Any, v_) if pyistuple(k_) k = pyconvert(Vector{Any}, k_) - pydel!(k_) + unsafe_pydel!(k_) self[k...] = v else k = pyconvert(Any, k_) @@ -69,7 +69,7 @@ end function pyjlraw_delitem(self, k_::Py) if pyistuple(k_) k = pyconvert(Vector{Any}, k_) - pydel!(k_) + unsafe_pydel!(k_) delete!(self, k...) else k = pyconvert(Any, k_) diff --git a/src/JlWrap/set.jl b/src/JlWrap/set.jl index 38c9b82d..fceefad0 100644 --- a/src/JlWrap/set.jl +++ b/src/JlWrap/set.jl @@ -37,7 +37,7 @@ function pyjlset_update(x::AbstractSet, vs_::Py) for v_ in vs_ v = pyconvert(eltype(x), v_) push!(x, v) - pydel!(v_) + unsafe_pydel!(v_) end Py(nothing) end @@ -46,7 +46,7 @@ function pyjlset_difference_update(x::AbstractSet, vs_::Py) for v_ in vs_ v = @pyconvert(eltype(x), v_, continue) delete!(x, v) - pydel!(v_) + unsafe_pydel!(v_) end Py(nothing) end @@ -56,7 +56,7 @@ function pyjlset_intersection_update(x::AbstractSet, vs_::Py) for v_ in vs_ v = @pyconvert(eltype(x), v_, continue) push!(vs, v) - pydel!(v_) + unsafe_pydel!(v_) end intersect!(x, vs) Py(nothing) @@ -67,7 +67,7 @@ function pyjlset_symmetric_difference_update(x::AbstractSet, vs_::Py) for v_ in vs_ v = pyconvert(eltype(x), v_) push!(vs, v) - pydel!(v_) + unsafe_pydel!(v_) end symdiff!(x, vs) Py(nothing) diff --git a/src/JlWrap/type.jl b/src/JlWrap/type.jl index ef079f5c..4fcc56d6 100644 --- a/src/JlWrap/type.jl +++ b/src/JlWrap/type.jl @@ -3,7 +3,7 @@ const pyjltypetype = pynew() function pyjltype_getitem(self::Type, k_) if pyistuple(k_) k = pyconvert(Vector{Any}, k_) - pydel!(k_) + unsafe_pydel!(k_) Py(self{k...}) else k = pyconvert(Any, k_) diff --git a/src/JlWrap/vector.jl b/src/JlWrap/vector.jl index 9f728625..42f05456 100644 --- a/src/JlWrap/vector.jl +++ b/src/JlWrap/vector.jl @@ -2,18 +2,18 @@ const pyjlvectortype = pynew() function pyjlvector_resize(x::AbstractVector, size_::Py) size = pyconvertarg(Int, size_, "size") - pydel!(size_) + unsafe_pydel!(size_) resize!(x, size) Py(nothing) end function pyjlvector_sort(x::AbstractVector, reverse_::Py, key_::Py) reverse = pyconvertarg(Bool, reverse_, "reverse") - pydel!(reverse_) + unsafe_pydel!(reverse_) key = pyconvertarg(Any, key_, "size") if key === nothing sort!(x, rev=reverse) - pydel!(key_) + unsafe_pydel!(key_) else sort!(x, rev=reverse, by=key) end @@ -36,7 +36,7 @@ end function pyjlvector_insert(x::AbstractVector, k_::Py, v_::Py) k = pyconvertarg(Int, k_, "index") - pydel!(k_) + unsafe_pydel!(k_) a = axes(x, 1) k′ = k < 0 ? (last(a) + 1 + k) : (first(a) + k) if checkbounds(Bool, x, k′) || k′ == last(a)+1 @@ -60,13 +60,13 @@ function pyjlvector_extend(x::AbstractVector, vs_::Py) v = pyconvert(eltype(x), v_) push!(x, v) end - pydel!(vs_) + unsafe_pydel!(vs_) Py(nothing) end function pyjlvector_pop(x::AbstractVector, k_::Py) k = pyconvertarg(Int, k_, "index") - pydel!(k_) + unsafe_pydel!(k_) a = axes(x, 1) k′ = k < 0 ? (last(a) + 1 + k) : (first(a) + k) if checkbounds(Bool, x, k′) diff --git a/src/PyMacro/PyMacro.jl b/src/PyMacro/PyMacro.jl index 04bf9718..e87f0049 100644 --- a/src/PyMacro/PyMacro.jl +++ b/src/PyMacro/PyMacro.jl @@ -19,7 +19,7 @@ Provides the `@py` macro. module PyMacro using ..Core -using ..Core: pyisnot, pynotin, BUILTINS, pynew, pycallargs, pydel!, pycopy!, pystr_intern!, pynulltuple, pytuple_setitem, pyset_add, pyisnull, unsafe_pynext, pydict_setitem, pylist_setitem, pynulllist, pybool_asbool, pythrow +using ..Core: pyisnot, pynotin, BUILTINS, pynew, pycallargs, unsafe_pydel!, pycopy!, pystr_intern!, pynulltuple, pytuple_setitem, pyset_add, pyisnew, unsafe_pynext, pydict_setitem, pylist_setitem, pynulllist, pybool_asbool, pythrow using MacroTools: MacroTools, @capture, isexpr @@ -147,7 +147,7 @@ end py_macro_assign(body, ans, ex) = push!(body, :($ans = $ex)) -py_macro_del(body, var, tmp) = if tmp; push!(body, :($pydel!($var))); end +py_macro_del(body, var, tmp) = if tmp; push!(body, :($unsafe_pydel!($var))); end ismacroexpr(ex, name) = isexpr(ex, :macrocall) && (ex.args[1] === Symbol(name) || ex.args[1] === GlobalRef(Base.Core, Symbol(name))) @@ -326,7 +326,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) end if af === :print # treat print as a special case since it is variadic - push!(body, :($pydel!($ans))) + push!(body, :($unsafe_pydel!($ans))) py_macro_assign(body, ans, nothing) return false else @@ -587,7 +587,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) tx = py_macro_lower(st, body, ans, ax) body2 = [] body3 = [] - tx && push!(body2, :($pydel!($ans))) + tx && push!(body2, :($unsafe_pydel!($ans))) ty = py_macro_lower(st, body2, ans, ay) t = tx || ty if t @@ -603,7 +603,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) tx = py_macro_lower(st, body, ans, ax) body2 = [] body3 = [] - tx && push!(body3, :($pydel!($ans))) + tx && push!(body3, :($unsafe_pydel!($ans))) ty = py_macro_lower(st, body3, ans, ay) t = tx || ty if t @@ -634,7 +634,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_del(body, y, ty) body2 = [] push!(body2, :($v = $unsafe_pynext($i))) - push!(body2, Expr(:if, :($pyisnull($v)), Expr(:block, :($pydel!($v)), :(break)))) + push!(body2, Expr(:if, :($pyisnew($v)), Expr(:block, :($unsafe_pydel!($v)), :(break)))) py_macro_lower_assign(st, body2, ax, v) py_macro_del(body2, v, true) tz = py_macro_lower(st, body2, z, az) diff --git a/src/PythonCall.jl b/src/PythonCall.jl index ca04e6b4..04bee33d 100644 --- a/src/PythonCall.jl +++ b/src/PythonCall.jl @@ -10,11 +10,11 @@ include("Core/Core.jl") include("Convert/Convert.jl") include("PyMacro/PyMacro.jl") include("Wrap/Wrap.jl") -include("JlWrap/JlWrap.jl") +include("JlCore/JlCore.jl") include("Compat/Compat.jl") # re-export everything -for m in [:Core, :Convert, :PyMacro, :Wrap, :JlWrap, :Compat] +for m in [:Core, :Convert, :PyMacro, :Wrap, :JlCore, :Compat] for k in names(@eval($m)) if k != m @eval using .$m: $k @@ -27,9 +27,6 @@ end for k in [:python_executable_path, :python_library_path, :python_library_handle, :python_version] @eval using .C: $k end -for k in [:pynew, :pyisnull, :pycopy!, :getptr, :pydel!, :unsafe_pynext, :PyNULL, :CONFIG] - @eval using .Core: $k -end for k in [:pyconvert_add_rule, :pyconvert_return, :pyconvert_unconverted, :PYCONVERT_PRIORITY_WRAP, :PYCONVERT_PRIORITY_ARRAY, :PYCONVERT_PRIORITY_CANONICAL, :PYCONVERT_PRIORITY_NORMAL, :PYCONVERT_PRIORITY_FALLBACK] @eval using .Convert: $k end @@ -37,9 +34,4 @@ for k in [:event_loop_on, :event_loop_off, :fix_qt_plugin_path] @eval using .Compat: $k end -# not API but used in tests -for k in [:pyjlanytype, :pyjlarraytype, :pyjlvectortype, :pyjlbinaryiotype, :pyjltextiotype, :pyjldicttype, :pyjlmoduletype, :pyjlintegertype, :pyjlrationaltype, :pyjlrealtype, :pyjlcomplextype, :pyjlsettype, :pyjltypetype] - @eval using .JlWrap: $k -end - end diff --git a/src/Wrap/PyArray.jl b/src/Wrap/PyArray.jl index af7933cf..bbc4f655 100644 --- a/src/Wrap/PyArray.jl +++ b/src/Wrap/PyArray.jl @@ -84,21 +84,21 @@ abstract type PyArraySource end function pyarray_make(::Type{A}, x::Py; array::Bool=true, buffer::Bool=true, copy::Bool=true) where {A<:PyArray} # TODO: try/catch is SLOW if an error is thrown, think about sending errors via return values instead A == Union{} && return pyconvert_unconverted() - if array && (xa = pygetattr(x, "__array_struct__", PyNULL); !pyisnull(xa)) + if array && (xa = pygetattr(x, "__array_struct__", PyNULL); !pyisnew(xa)) try return pyarray_make(A, x, PyArraySource_ArrayStruct(x, xa)) catch exc @debug "failed to make PyArray from __array_struct__" exc=exc end end - if array && (xi = pygetattr(x, "__array_interface__", PyNULL); !pyisnull(xi)) + if array && (xi = pygetattr(x, "__array_interface__", PyNULL); !pyisnew(xi)) try return pyarray_make(A, x, PyArraySource_ArrayInterface(x, xi)) catch exc @debug "failed to make PyArray from __array_interface__" exc=exc end end - if buffer && C.PyObject_CheckBuffer(getptr(x)) + if buffer && C.PyObject_CheckBuffer(x) try return pyarray_make(A, x, PyArraySource_Buffer(x)) catch exc @@ -107,14 +107,14 @@ function pyarray_make(::Type{A}, x::Py; array::Bool=true, buffer::Bool=true, cop end if copy && array && pyhasattr(x, "__array__") y = x.__array__() - if (ya = pygetattr(y, "__array_struct__", PyNULL); !pyisnull(ya)) + if (ya = pygetattr(y, "__array_struct__", PyNULL); !pyisnew(ya)) try return pyarray_make(A, y, PyArraySource_ArrayStruct(y, ya)) catch exc @debug "failed to make PyArray from __array__().__array_interface__" exc=exc end end - if (yi = pygetattr(y, "__array_interface__", PyNULL); !pyisnull(yi)) + if (yi = pygetattr(y, "__array_interface__", PyNULL); !pyisnew(yi)) try return pyarray_make(A, y, PyArraySource_ArrayInterface(y, yi)) catch exc @@ -208,12 +208,12 @@ function PyArraySource_ArrayInterface(x::Py, d::Py=x.__array_interface__) if pyistuple(data) ptr = Ptr{Cvoid}(pyconvert(UInt, data[0])) readonly = pyconvert(Bool, data[1]) - pydel!(data) + unsafe_pydel!(data) handle = Py((x, d)) else memview = @py memoryview(data === None ? x : data) - pydel!(data) - buf = UnsafePtr(C.PyMemoryView_GET_BUFFER(getptr(memview))) + unsafe_pydel!(data) + buf = UnsafePtr(C.PyMemoryView_GET_BUFFER(memview)) ptr = buf.buf[!] readonly = buf.readonly[] != 0 handle = Py((x, memview)) @@ -269,7 +269,7 @@ pyarray_typestrdescr_to_type(ts::String, descr::Py) = begin elseif etc == 'O' return UnsafePyObject elseif etc == 'V' - pyisnull(descr) && error("not supported: void dtype with null descr") + pyisnew(descr) && error("not supported: void dtype with null descr") sz = parse(Int, ts[3:end]) T = pyarray_descr_to_type(descr) sizeof(T) == sz || error("size mismatch: itemsize=$sz but sizeof(descr)=$(sizeof(T))") @@ -339,7 +339,7 @@ function pyarray_get_R(src::PyArraySource_ArrayInterface) typestr = pyconvert(String, src.dict["typestr"]) descr = @py @jl(src.dict).get("descr") R = pyarray_typestrdescr_to_type(typestr, descr)::DataType - pydel!(descr) + unsafe_pydel!(descr) return R end @@ -352,7 +352,7 @@ pyarray_get_size(src::PyArraySource_ArrayInterface, ::Val{N}) where {N} = pyconv function pyarray_get_strides(src::PyArraySource_ArrayInterface, ::Val{N}, ::Type{R}, size::NTuple{N,Int}) where {R,N} @py strides = @jl(src.dict).get("strides") if pyisnone(strides) - pydel!(strides) + unsafe_pydel!(strides) return Utils.size_to_cstrides(sizeof(R), size) else return pyconvert(NTuple{N,Int}, strides) @@ -371,8 +371,8 @@ struct PyArraySource_ArrayStruct <: PyArraySource info :: C.PyArrayInterface end function PyArraySource_ArrayStruct(x::Py, capsule::Py=x.__array_struct__) - name = C.PyCapsule_GetName(getptr(capsule)) - ptr = C.PyCapsule_GetPointer(getptr(capsule), name) + name = C.PyCapsule_GetName(capsule) + ptr = C.PyCapsule_GetPointer(capsule, name) info = unsafe_load(Ptr{C.PyArrayInterface}(ptr)) @assert info.two == 2 return PyArraySource_ArrayStruct(x, capsule, info) @@ -494,7 +494,7 @@ struct PyArraySource_Buffer <: PyArraySource end function PyArraySource_Buffer(x::Py) memview = pybuiltins.memoryview(x) - buf = C.UnsafePtr(C.PyMemoryView_GET_BUFFER(getptr(memview))) + buf = C.UnsafePtr(C.PyMemoryView_GET_BUFFER(memview)) PyArraySource_Buffer(x, memview, buf) end diff --git a/src/Wrap/PyDict.jl b/src/Wrap/PyDict.jl index 494ec421..90f30c3f 100644 --- a/src/Wrap/PyDict.jl +++ b/src/Wrap/PyDict.jl @@ -25,7 +25,7 @@ Base.length(x::PyDict) = Int(pylen(x)) function Base.iterate(x::PyDict{K,V}, it::Py=pyiter(x)) where {K,V} k_ = unsafe_pynext(it) - pyisnull(k_) && return nothing + pyisnew(k_) && return nothing v_ = pygetitem(x, k_) k = pyconvert(K, k_) v = pyconvert(V, v_) @@ -34,7 +34,7 @@ end function Base.iterate(x::Base.KeySet{K,PyDict{K,V}}, it::Py=pyiter(x.dict)) where {K,V} k_ = unsafe_pynext(it) - pyisnull(k_) && return nothing + pyisnew(k_) && return nothing k = pyconvert(K, k_) return (k, it) end @@ -60,7 +60,7 @@ function Base.delete!(x::PyDict{K,V}, k) where {K,V} end function Base.empty!(x::PyDict) - pydel!(@py x.clear()) + unsafe_pydel!(@py x.clear()) return x end diff --git a/src/Wrap/PyIO.jl b/src/Wrap/PyIO.jl index ecc35895..be45bf4c 100644 --- a/src/Wrap/PyIO.jl +++ b/src/Wrap/PyIO.jl @@ -76,7 +76,7 @@ function PyIO(f::Function, o; opts...) try return f(io) finally - pydel!(io.py) + unsafe_pydel!(io.py) end end @@ -84,8 +84,8 @@ end function putobuf(io::PyIO) if !isempty(io.obuf) data = io.text ? pystr_fromUTF8(io.obuf) : pybytes(io.obuf) - pydel!(@py io.write(data)) - pydel!(data) + unsafe_pydel!(@py io.write(data)) + unsafe_pydel!(data) empty!(io.obuf) end return @@ -102,20 +102,20 @@ function getibuf(io::PyIO) else append!(io.ibuf, pybytes_asvector(data)) end - pydel!(data) + unsafe_pydel!(data) end return end function Base.flush(io::PyIO) putobuf(io) - pydel!(@py io.flush()) + unsafe_pydel!(@py io.flush()) return end function Base.close(io::PyIO) flush(io) - pydel!(@py io.close()) + unsafe_pydel!(@py io.close()) return end @@ -203,13 +203,13 @@ function Base.seek(io::PyIO, pos::Integer) putobuf(io) empty!(io.ibuf) io.eof = false - pydel!(@py io.seek(pos)) + unsafe_pydel!(@py io.seek(pos)) return io end function Base.truncate(io::PyIO, pos::Integer) seek(io, position(io)) - pydel!(@py io.truncate(pos)) + unsafe_pydel!(@py io.truncate(pos)) return io end @@ -217,7 +217,7 @@ function Base.seekstart(io::PyIO) putobuf(io) empty!(io.ibuf) io.eof = false - pydel!(@py io.seek(0)) + unsafe_pydel!(@py io.seek(0)) return io end @@ -225,7 +225,7 @@ function Base.seekend(io::PyIO) putobuf(io) empty!(io.ibuf) io.eof = false - pydel!(@py io.seek(0, 2)) + unsafe_pydel!(@py io.seek(0, 2)) return io end @@ -241,7 +241,7 @@ function Base.skip(io::PyIO, n::Integer) if 0 ≤ n ≤ io.ibuflen read(io, n) else - pydel!(@py io.seek(@jl(n - length(io.ibuf)), 1)) + unsafe_pydel!(@py io.seek(@jl(n - length(io.ibuf)), 1)) empty!(io.ibuf) io.eof = false end diff --git a/src/Wrap/PyIterable.jl b/src/Wrap/PyIterable.jl index 807e306d..2168fdec 100644 --- a/src/Wrap/PyIterable.jl +++ b/src/Wrap/PyIterable.jl @@ -19,8 +19,8 @@ Base.eltype(::Type{PyIterable{T}}) where {T} = T function Base.iterate(x::PyIterable{T}, it::Py=pyiter(x)) where {T} y = unsafe_pynext(it) - if pyisnull(y) - pydel!(it) + if pyisnew(y) + unsafe_pydel!(it) return nothing else return (pyconvert(T, y), it) diff --git a/src/Wrap/PyList.jl b/src/Wrap/PyList.jl index 1eac6479..ee43639f 100644 --- a/src/Wrap/PyList.jl +++ b/src/Wrap/PyList.jl @@ -37,12 +37,12 @@ end Base.@propagate_inbounds function Base.insert!(x::PyList{T}, i::Integer, v) where {T} @boundscheck (i==length(x)+1 || checkbounds(x, i)) - pydel!(@py x.insert(@jl(i-1), @jl(convert(T, v)))) + unsafe_pydel!(@py x.insert(@jl(i-1), @jl(convert(T, v)))) return x end function Base.push!(x::PyList{T}, v) where {T} - pydel!(@py x.append(@jl(convert(T, v)))) + unsafe_pydel!(@py x.append(@jl(convert(T, v)))) return x end @@ -78,12 +78,12 @@ Base.@propagate_inbounds function Base.popfirst!(x::PyList{T}) where {T} end function Base.reverse!(x::PyList) - pydel!(@py x.reverse()) + unsafe_pydel!(@py x.reverse()) return x end function Base.empty!(x::PyList) - pydel!(@py x.clear()) + unsafe_pydel!(@py x.clear()) return x end diff --git a/src/Wrap/PySet.jl b/src/Wrap/PySet.jl index d2da19af..f194986c 100644 --- a/src/Wrap/PySet.jl +++ b/src/Wrap/PySet.jl @@ -26,8 +26,8 @@ Base.isempty(x::PySet) = length(x) == 0 function Base.iterate(x::PySet{T}, it::Py=pyiter(x)) where {T} y = unsafe_pynext(it) - if pyisnull(y) - pydel!(it) + if pyisnew(y) + unsafe_pydel!(it) return nothing else return (pyconvert(T, y), it) @@ -48,17 +48,17 @@ function Base.in(v, x::PySet{T}) where {T} end function Base.push!(x::PySet{T}, v) where {T} - pydel!(@py x.add(@jl(convert(T, v)))) + unsafe_pydel!(@py x.add(@jl(convert(T, v)))) return x end function Base.delete!(x::PySet{T}, v) where {T} if v isa T - pydel!(@py x.discard(v)) + unsafe_pydel!(@py x.discard(v)) else r = pyconvert_tryconvert(T, v) if !pyconvert_isunconverted(r) - pydel!(@py x.discard(@jl pyconvert_result(T, r))) + unsafe_pydel!(@py x.discard(@jl pyconvert_result(T, r))) end end return x @@ -88,7 +88,7 @@ function Base.pop!(x::PySet, v, d) end function Base.empty!(x::PySet) - pydel!(@py x.clear()) + unsafe_pydel!(@py x.clear()) return x end diff --git a/src/Wrap/Wrap.jl b/src/Wrap/Wrap.jl index 4f934b28..f9eae20d 100644 --- a/src/Wrap/Wrap.jl +++ b/src/Wrap/Wrap.jl @@ -6,7 +6,7 @@ Defines Julia wrappers around Python objects, including `PyList`, `PyDict`, `PyA module Wrap using ..Core -using ..Core: C, Utils, @autopy, unsafe_pynext, pyisnull, PyNULL, getptr, pydel!, pybytes_asvector, pystr_asUTF8vector, pystr_fromUTF8, incref, decref, pynew, pyisnone, pyistuple, pyisstr +using ..Core: C, Utils, @autopy, unsafe_pynext, pyisnew, PyNULL, getptr, unsafe_pydel!, pybytes_asvector, pystr_asUTF8vector, pystr_fromUTF8, incref, decref, pynew, pyisnone, pyistuple, pyisstr using ..Convert: pyconvert, pyconvert_tryconvert, pyconvert_unconverted, pyconvert_isunconverted, pyconvert_return, pyconvert_result using ..PyMacro diff --git a/test/Core.jl b/test/Core.jl index 9e06125c..f653297b 100644 --- a/test/Core.jl +++ b/test/Core.jl @@ -212,7 +212,7 @@ @test Base.Docs.getdoc(Py(nothing)) isa Markdown.MD @test Base.Docs.getdoc(Py(12)) isa Markdown.MD @test Base.Docs.getdoc(pybuiltins.int) isa Markdown.MD - @test Base.Docs.getdoc(PythonCall.PyNULL) === nothing + @test Base.Docs.getdoc(pynew()) isa Markdown.MD end end @@ -222,13 +222,13 @@ end # unsafe_pynext it = pyiter(pyrange(2)) x = PythonCall.unsafe_pynext(it) - @test !PythonCall.pyisnull(x) + @test !PythonCall.pyisnew(x) @test pyeq(Bool, x, 0) x = PythonCall.unsafe_pynext(it) - @test !PythonCall.pyisnull(x) + @test !PythonCall.pyisnew(x) @test pyeq(Bool, x, 1) x = PythonCall.unsafe_pynext(it) - @test PythonCall.pyisnull(x) + @test PythonCall.pyisnew(x) # pynext it = pyiter(pyrange(2)) x = pynext(it)