Skip to content

Commit 4d9a418

Browse files
authored
Merge pull request #4 from cjdoris/c
Big rewrite
2 parents 940d944 + 8f3c10a commit 4d9a418

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+7404
-4033
lines changed

juliapy/julia/__init__.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
_CONFIG = dict()
1+
CONFIG = dict()
22

3-
def _init_():
3+
def init():
44
import os, os.path, sys, ctypes as c, types, shutil, subprocess
55
libpath = os.environ.get('JULIAPY_LIB')
66
if libpath is None:
@@ -12,52 +12,47 @@ def _init_():
1212
else:
1313
if not os.path.isfile(exepath):
1414
raise Exception('JULIAPY_EXE=%s does not exist' % repr(exepath))
15-
_CONFIG['exepath'] = exepath
15+
CONFIG['exepath'] = exepath
1616
libpath = subprocess.run([exepath, '-e', 'import Libdl; print(abspath(Libdl.dlpath("libjulia")))'], stdout=(subprocess.PIPE)).stdout.decode('utf8')
1717
else:
1818
if not os.path.isfile(libpath):
1919
raise Exception('JULIAPY_LIB=%s does not exist' % repr(libpath))
20-
_CONFIG['libpath'] = libpath
20+
CONFIG['libpath'] = libpath
2121
try:
2222
d = os.getcwd()
2323
os.chdir(os.path.dirname(libpath))
2424
lib = c.CDLL(libpath)
2525
finally:
2626
os.chdir(d)
2727

28-
_CONFIG['lib'] = lib
28+
CONFIG['lib'] = lib
2929
lib.jl_init__threading.argtypes = []
3030
lib.jl_init__threading.restype = None
3131
lib.jl_init__threading()
3232
lib.jl_eval_string.argtypes = [c.c_char_p]
3333
lib.jl_eval_string.restype = c.c_void_p
3434
res = lib.jl_eval_string(
3535
'''
36-
ENV["PYTHONJL_LIBPTR"] = "{}"
37-
import Python
38-
Python.with_gil() do
39-
Python.pyimport("sys").modules["julia"].Main = Python.pyjl(Main)
36+
try
37+
ENV["PYTHONJL_LIBPTR"] = "{}"
38+
import Python
39+
Python.with_gil() do
40+
Python.pyimport("sys").modules["julia"].Main = Python.pyjl(Main)
41+
end
42+
catch err
43+
@error "Error loading Python.jl" err=err
44+
rethrow()
4045
end
4146
'''.format(c.pythonapi._handle).encode('utf8'))
4247
if res is None:
4348
raise Exception('Python.jl did not start properly. Ensure that the Python package is installed in Julia.')
4449

45-
class Wrapper(types.ModuleType):
46-
47-
def __getattr__(self, k):
48-
return getattr(self.Main, k)
49-
50-
def __dir__(self):
51-
return super().__dir__() + self.Main.__dir__()
52-
53-
sys.modules['julia'].__class__ = Wrapper
54-
55-
_init_()
56-
del _init_
50+
init()
51+
del init
5752

5853
Core = Main.Core
5954
Base = Main.Base
6055
Python = Main.Python
6156

62-
def _import(*names):
63-
Main.eval(Base.Meta.parse('import ' + ', '.join(names)))
57+
def newmodule(name):
58+
return Base.Module(Base.Symbol(name))

src/PyArray.jl

Lines changed: 126 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,101 +3,147 @@
33
44
Interpret the Python array `o` as a Julia array.
55
6-
Type parameters which are not given or set to `missing` are inferred:
6+
The input may be anything supporting the buffer protocol or the numpy array interface.
7+
This includes, `bytes`, `bytearray`, `array.array`, `numpy.ndarray`, `pandas.Series`.
8+
79
- `T` is the (Julia) element type.
810
- `N` is the number of dimensions.
911
- `R` is the type of elements of the underlying buffer (which may be different from `T` to allow some basic conversion).
1012
- `M` is true if the array is mutable.
1113
- `L` is true if the array supports fast linear indexing.
1214
"""
1315
mutable struct PyArray{T,N,R,M,L} <: AbstractArray{T,N}
14-
o :: PyObject
16+
ref :: PyRef
1517
ptr :: Ptr{R}
1618
size :: NTuple{N,Int}
1719
length :: Int
1820
bytestrides :: NTuple{N,Int}
1921
handle :: Any
2022
end
21-
2223
const PyVector{T,R,M,L} = PyArray{T,1,R,M,L}
2324
const PyMatrix{T,R,M,L} = PyArray{T,2,R,M,L}
2425
export PyArray, PyVector, PyMatrix
2526

26-
function PyArray{T,N,R,M,L}(o::PyObject, info=pyarray_info(o)) where {T,N,R,M,L}
27-
# R - buffer element type
28-
if R === missing
29-
return PyArray{T, N, info.eltype, M, L}(o, info)
30-
elseif R isa Type
31-
Base.allocatedinline(R) || error("source must be allocated inline, got R=$R")
32-
Base.aligned_sizeof(R) == info.elsize || error("source elements must have size $(info.elsize), got R=$R")
33-
else
34-
error("R must be missing or a type")
35-
end
27+
ispyreftype(::Type{<:PyArray}) = true
28+
pyptr(x::PyArray) = pyptr(x.ref)
29+
Base.unsafe_convert(::Type{CPyPtr}, x::PyArray) = pyptr(x.ref)
30+
C.PyObject_TryConvert__initial(o, ::Type{T}) where {T<:PyArray} = CTryConvertRule_trywrapref(o, T)
3631

32+
function PyArray{T,N,R,M,L}(o::PyRef, info) where {T,N,R,M,L}
3733
# T - array element type
38-
if T === missing
39-
return PyArray{pyarray_default_T(R), N, R, M, L}(o, info)
40-
elseif T isa Type
41-
# great
42-
else
43-
error("T must be missing or a type")
44-
end
34+
T isa Type || error("T must be a type, got T=$T")
4535

46-
# N
47-
if N === missing
48-
return PyArray{T, Int(info.ndims), R, M, L}(o, info)
49-
elseif N isa Int
50-
N == info.ndims || error("source dimension is $(info.ndims), got N=$N")
51-
# great
52-
elseif N isa Integer
53-
return PyArray{T, Int(N), R, M, L}(o, info)
54-
else
55-
error("N must be missing or an integer")
56-
end
36+
# N - number of dimensions
37+
N isa Integer || error("N must be an integer, got N=$N")
38+
N isa Int || return PyArray{T, Int(N), R, M, L}(o, info)
39+
N == info.ndims || error("source dimension is $(info.ndims), got N=$N")
5740

58-
# M
59-
if M === missing
60-
return PyArray{T, N, R, Bool(info.mutable), L}(o, info)
61-
elseif M === true
62-
info.mutable || error("source is immutable, got L=$L")
63-
elseif M === false
64-
# great
65-
else
66-
error("M must be missing, true or false")
67-
end
41+
# R - buffer element type
42+
R isa Type || error("R must be a type, got R=$R")
43+
Base.allocatedinline(R) || error("source elements must be allocated inline, got R=$R")
44+
Base.aligned_sizeof(R) == info.elsize || error("source elements must have size $(info.elsize), got R=$R")
6845

69-
bytestrides = NTuple{N, Int}(info.bytestrides)
70-
size = NTuple{N, Int}(info.size)
46+
# M - mutable
47+
M isa Bool || error("M must be true or false, got M=$M")
48+
!M || info.mutable || error("source is immutable, got M=$M")
7149

72-
# L
73-
if L === missing
74-
return PyArray{T, N, R, M, N ≤ 1 || size_to_fstrides(bytestrides[1], size...) == bytestrides}(o, info)
75-
elseif L === true
76-
N 1 || size_to_fstrides(bytestrides[1], size...) == bytestrides || error("not linearly indexable")
77-
elseif L === false
78-
# great
79-
else
80-
error("L must be missing, true or false")
81-
end
50+
bytestrides = info.bytestrides
51+
size = info.size
52+
53+
# L - linear indexable
54+
L isa Bool || error("L must be true or false, got L=$L")
55+
!L || N 1 || size_to_fstrides(bytestrides[1], size...) == bytestrides || error("not linearly indexable, got L=$L")
8256

83-
PyArray{T, N, R, M, L}(o, Ptr{R}(info.ptr), size, N==0 ? 1 : prod(size), bytestrides, info.handle)
57+
PyArray{T, N, R, M, L}(PyRef(o), Ptr{R}(info.ptr), size, N==0 ? 1 : prod(size), bytestrides, info.handle)
8458
end
85-
PyArray{T,N,R,M}(o) where {T,N,R,M} = PyArray{T,N,R,M,missing}(o)
86-
PyArray{T,N,R}(o) where {T,N,R} = PyArray{T,N,R,missing}(o)
87-
PyArray{T,N}(o) where {T,N} = PyArray{T,N,missing}(o)
88-
PyArray{T}(o) where {T} = PyArray{T,missing}(o)
89-
PyArray(o) where {} = PyArray{missing}(o)
90-
91-
pyobject(x::PyArray) = x.o
92-
93-
function pyarray_info(o::PyObject)
94-
# TODO: support the numpy array interface too
95-
b = PyBuffer(o, C.PyBUF_RECORDS_RO)
96-
(ndims=b.ndim, eltype=b.eltype, elsize=b.itemsize, mutable=!b.readonly, bytestrides=b.strides, size=b.shape, ptr=b.buf, handle=b)
59+
PyArray{T,N,R,M}(o::PyRef, info) where {T,N,R,M} = PyArray{T,N,R,M, N≤1 || size_to_fstrides(info.bytestrides[1], info.size...) == info.bytestrides}(o, info)
60+
PyArray{T,N,R}(o::PyRef, info) where {T,N,R} = PyArray{T,N,R, info.mutable}(o, info)
61+
PyArray{T,N}(o::PyRef, info) where {T,N} = PyArray{T,N, info.eltype}(o, info)
62+
PyArray{T}(o::PyRef, info) where {T} = PyArray{T, info.ndims}(o, info)
63+
PyArray{<:Any,N}(o::PyRef, info) where {N} = PyArray{pyarray_default_T(info.eltype), N}(o, info)
64+
PyArray(o::PyRef, info) = PyArray{pyarray_default_T(info.eltype)}(o, info)
65+
66+
(::Type{A})(o; opts...) where {A<:PyArray} = begin
67+
ref = PyRef(o)
68+
info = pyarray_info(ref; opts...)
69+
info = (
70+
ndims = Int(info.ndims),
71+
eltype = info.eltype :: Type,
72+
elsize = Int(info.elsize),
73+
mutable = info.mutable :: Bool,
74+
bytestrides = NTuple{Int(info.ndims), Int}(info.bytestrides),
75+
size = NTuple{Int(info.ndims), Int}(info.size),
76+
ptr = Ptr{Cvoid}(info.ptr),
77+
handle = info.handle,
78+
)
79+
A(ref, info)
80+
end
81+
82+
function pyarray_info(ref; buffer=true, array=true, copy=true)
83+
if array && pyhasattr(ref, "__array_interface__")
84+
pyconvertdescr(x) = begin
85+
@py ```
86+
def convert(x):
87+
def fix(x):
88+
a = x[0]
89+
a = (a, a) if isinstance(a, str) else (a[0], a[1])
90+
b = x[1]
91+
c = x[2] if len(x)>2 else 1
92+
return (a, b, c)
93+
if x is None or isinstance(x, str):
94+
return x
95+
else:
96+
return [fix(y) for y in x]
97+
$(r::Union{Nothing,String,Vector{Tuple{Tuple{String,String}, PyObject, Int}}}) = convert($x)
98+
```
99+
r isa Vector ? [(a, pyconvertdescr(b), c) for (a,b,c) in r] : r
100+
end
101+
ai = pygetattr(ref, "__array_interface__")
102+
pyconvert(Int, ai["version"]) == 3 || error("wrong version")
103+
size = pyconvert(Tuple{Vararg{Int}}, ai["shape"])
104+
ndims = length(size)
105+
typestr = pyconvert(String, ai["typestr"])
106+
descr = pyconvertdescr(ai.get("descr"))
107+
eltype = pytypestrdescr_to_type(typestr, descr)
108+
elsize = Base.aligned_sizeof(eltype)
109+
strides = pyconvert(Union{Nothing, Tuple{Vararg{Int}}}, ai.get("strides"))
110+
strides === nothing && (strides = size_to_cstrides(elsize, size...))
111+
pyis(ai.get("mask"), pynone()) || error("mask not supported")
112+
offset = pyconvert(Union{Nothing, Int}, ai.get("offset"))
113+
offset === nothing && (offset = 0)
114+
data = pyconvert(Union{PyObject, Tuple{UInt, Bool}, Nothing}, ai.get("data"))
115+
if data isa Tuple
116+
ptr = Ptr{Cvoid}(data[1])
117+
mutable = !data[2]
118+
handle = (ref, ai)
119+
else
120+
buf = PyBuffer(data === nothing ? ref : data)
121+
ptr = buf.buf
122+
mutable = !buf.readonly
123+
handle = (ref, ai, buf)
124+
end
125+
return (ndims=ndims, eltype=eltype, elsize=elsize, mutable=mutable, bytestrides=strides, size=size, ptr=ptr, handle=handle)
126+
end
127+
if array && pyhasattr(ref, "__array_struct__")
128+
# TODO
129+
end
130+
if buffer && C.PyObject_CheckBuffer(ref)
131+
try
132+
b = PyBuffer(ref, C.PyBUF_RECORDS_RO)
133+
return (ndims=b.ndim, eltype=b.eltype, elsize=b.itemsize, mutable=!b.readonly, bytestrides=b.strides, size=b.shape, ptr=b.buf, handle=b)
134+
catch
135+
end
136+
end
137+
if array && copy && pyhasattr(ref, "__array__")
138+
try
139+
return pyarray_info(pycall(PyRef, pygetattr(PyRef, ref, "__array__")); buffer=buffer, array=array, copy=false)
140+
catch
141+
end
142+
end
143+
error("given object does not support the buffer protocol or array interface")
97144
end
98145

99146
Base.isimmutable(x::PyArray{T,N,R,M,L}) where {T,N,R,M,L} = !M
100-
Base.pointer(x::PyArray{T,N,T}) where {T,N} = x.ptr
101147
Base.size(x::PyArray) = x.size
102148
Base.length(x::PyArray) = x.length
103149
Base.IndexStyle(::Type{PyArray{T,N,R,M,L}}) where {T,N,R,M,L} = L ? Base.IndexLinear() : Base.IndexCartesian()
@@ -120,13 +166,23 @@ Base.@propagate_inbounds Base.setindex!(x::PyArray{T,N,R,true,L}, v, i::Vararg{I
120166
end
121167

122168
pyarray_default_T(::Type{R}) where {R} = R
123-
pyarray_default_T(::Type{CPyObjRef}) = PyObject
169+
pyarray_default_T(::Type{C.PyObjectRef}) = PyObject
124170

125171
pyarray_load(::Type{T}, p::Ptr{T}) where {T} = unsafe_load(p)
126-
pyarray_load(::Type{T}, p::Ptr{CPyObjRef}) where {T} = (o=unsafe_load(p).ptr; o==C_NULL ? throw(UndefRefError()) : pyconvert(T, pyborrowedobject(o)))
172+
pyarray_load(::Type{T}, p::Ptr{C.PyObjectRef}) where {T} = begin
173+
o = unsafe_load(p).ptr
174+
isnull(o) && throw(UndefRefError())
175+
ism1(C.PyObject_Convert(o, T)) && pythrow()
176+
takeresult(T)
177+
end
127178

128179
pyarray_store!(p::Ptr{T}, v::T) where {T} = unsafe_store!(p, v)
129-
pyarray_store!(p::Ptr{CPyObjRef}, v::T) where {T} = (C.Py_DecRef(unsafe_load(p).ptr); unsafe_store!(p, CPyObjRef(pyptr(pyincref!(pyobject(v))))))
180+
pyarray_store!(p::Ptr{C.PyObjectRef}, v::T) where {T} = begin
181+
o = C.PyObject_From(v)
182+
isnull(o) && pythrow()
183+
C.Py_DecRef(unsafe_load(p).ptr)
184+
unsafe_store!(p, C.PyObjectRef(o))
185+
end
130186

131187
pyarray_offset(x::PyArray{T,N,R,M,true}, i::Int) where {T,N,R,M} =
132188
N==0 ? 0 : (i-1) * x.bytestrides[1]

src/PyBuffer.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
PyBuffer(o)
2+
PyBuffer(o, [flags=C.PyBUF_FULL_RO])
33
44
A reference to the underlying buffer of `o`, if it satisfies the buffer protocol.
55
@@ -20,16 +20,15 @@ Has the following properties:
2020
"""
2121
mutable struct PyBuffer
2222
info :: Array{C.Py_buffer, 0}
23-
function PyBuffer(o::PyObject, flags::Integer=C.PyBUF_FULL_RO)
23+
function PyBuffer(o, flags::Integer=C.PyBUF_FULL_RO)
2424
info = fill(C.Py_buffer())
2525
check(C.PyObject_GetBuffer(o, pointer(info), flags))
2626
b = new(info)
2727
finalizer(b) do b
2828
if CONFIG.isinitialized
29-
err = with_gil() do
29+
with_gil(false) do
3030
C.PyBuffer_Release(pointer(b.info))
3131
end
32-
check(err)
3332
end
3433
end
3534
b

src/PyCode.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
mutable struct PyCode
2+
ref :: PyRef
3+
code :: String
4+
filename :: String
5+
mode :: Symbol
6+
PyCode(code::String, filename::String, mode::Symbol) = begin
7+
mode in (:exec, :eval) || error("invalid mode $(repr(mode))")
8+
new(PyRef(), code, filename, mode)
9+
end
10+
end
11+
export PyCode
12+
13+
ispyreftype(::Type{PyCode}) = true
14+
pyptr(co::PyCode) = begin
15+
ptr = co.ref.ptr
16+
if isnull(ptr)
17+
ptr = co.ref.ptr = C.Py_CompileString(co.code, co.filename, co.mode == :exec ? C.Py_file_input : co.mode == :eval ? C.Py_eval_input : error("invalid mode $(repr(co.mode))"))
18+
end
19+
ptr
20+
end
21+
Base.unsafe_convert(::Type{CPyPtr}, x::PyCode) = checknull(pyptr(x))

0 commit comments

Comments
 (0)