3
3
4
4
Interpret the Python array `o` as a Julia array.
5
5
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
+
7
9
- `T` is the (Julia) element type.
8
10
- `N` is the number of dimensions.
9
11
- `R` is the type of elements of the underlying buffer (which may be different from `T` to allow some basic conversion).
10
12
- `M` is true if the array is mutable.
11
13
- `L` is true if the array supports fast linear indexing.
12
14
"""
13
15
mutable struct PyArray{T,N,R,M,L} <: AbstractArray{T,N}
14
- o :: PyObject
16
+ ref :: PyRef
15
17
ptr :: Ptr{R}
16
18
size :: NTuple{N,Int}
17
19
length :: Int
18
20
bytestrides :: NTuple{N,Int}
19
21
handle :: Any
20
22
end
21
-
22
23
const PyVector{T,R,M,L} = PyArray{T,1 ,R,M,L}
23
24
const PyMatrix{T,R,M,L} = PyArray{T,2 ,R,M,L}
24
25
export PyArray, PyVector, PyMatrix
25
26
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)
36
31
32
+ function PyArray {T,N,R,M,L} (o:: PyRef , info) where {T,N,R,M,L}
37
33
# 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 " )
45
35
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 " )
57
40
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 " )
68
45
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 " )
71
49
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 " )
82
56
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)
84
58
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" )
97
144
end
98
145
99
146
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
101
147
Base. size (x:: PyArray ) = x. size
102
148
Base. length (x:: PyArray ) = x. length
103
149
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
120
166
end
121
167
122
168
pyarray_default_T (:: Type{R} ) where {R} = R
123
- pyarray_default_T (:: Type{CPyObjRef } ) = PyObject
169
+ pyarray_default_T (:: Type{C.PyObjectRef } ) = PyObject
124
170
125
171
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
127
178
128
179
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
130
186
131
187
pyarray_offset (x:: PyArray{T,N,R,M,true} , i:: Int ) where {T,N,R,M} =
132
188
N== 0 ? 0 : (i- 1 ) * x. bytestrides[1 ]
0 commit comments