4
4
import numpy as np
5
5
6
6
7
- DEFAULT_SHAPE = (2 ,) * 2
8
- BYTES_BUF_ORDER = 'A'
7
+ DEFAULT_SHAPE = tuple ()
9
8
10
9
11
- def custom_as_ctypes_type (dtype : np .dtype ):
12
- """Wrapper around `numpy.ctypeslib.as_ctypes_type` that maps 'O' to py_object"""
13
- if not dtype .hasobject :
14
- return np .ctypeslib .as_ctypes_type (dtype )
15
-
16
- if dtype .fields is None :
17
- # Simple dtype or array
18
- if dtype .subdtype :
19
- # Handle sub-array types like ('O', 3)
20
- base , shape = dtype .subdtype
21
- new_base = custom_as_ctypes_type (base )
22
- for dim in reversed (shape ):
23
- new_base = new_base * dim
24
- return new_base
25
- else :
26
- # Simple object (non-objects are already handled)
27
- assert dtype == np .dtype (object )
28
- return ctypes .py_object
29
-
30
- # Structured dtype
31
- new_fields = []
32
- for name , (field_dtype , * _ ) in dtype .fields .items ():
33
- new_field_dtype = custom_as_ctypes_type (field_dtype )
34
- new_fields .append ((name , new_field_dtype ))
35
-
36
- class CustomStruct (ctypes .Structure ):
37
- _pack_ = dtype .alignment
38
- _fields_ = new_fields
39
-
40
- return CustomStruct
41
-
42
-
43
- class SampleScalar :
44
- _ndarr : np .ndarray
10
+ class ScalarType :
11
+ shape : tuple [int , ...]
12
+ dtype : np .dtype
45
13
46
14
def __init__ (self , * , shape : tuple [int , ...] = DEFAULT_SHAPE , dtype = None ):
47
- print ( f'TODO: scalar.py: SampleScalar.__init__( { shape = } , { dtype = } )' )
48
- self ._ndarr = np .empty ( shape , dtype = dtype )
15
+ self . shape = shape
16
+ self .dtype = np .dtype ( dtype )
49
17
50
- def copy (self ) -> 'SampleScalar' :
51
- return type (self )(shape = self ._ndarr . shape , dtype = self ._ndarr . dtype )
18
+ def __repr__ (self ) -> str :
19
+ return f' { type (self ). __name__ } (shape={ self .shape } , dtype={ self .dtype } )'
52
20
53
21
@property
54
22
def elsize (self ) -> int :
55
- return self ._ndarr .nbytes
23
+ """Overall size of DType item (to initialize `PyArray_Descr::elsize`)"""
24
+ # Consider @cashed_property
25
+ return np .empty (self .shape , self .dtype ).nbytes
56
26
57
27
@property
58
28
def alignment (self ) -> int :
59
- return self ._ndarr .dtype .alignment
29
+ """Alignment of DType item (to initialize `PyArray_Descr::alignment`)"""
30
+ return self .dtype .alignment
60
31
61
32
@property
62
33
def hasobject (self ) -> bool :
63
- return self . _ndarr . dtype . hasobject
34
+ """Whether DType contain pointers to Python objects
64
35
65
- def is_compatible ( self , other : 'SampleScalar' ) -> bool :
66
- print ( f'scalar.py: SampleScalar.is_compatible( { other } )' )
67
- return self ._ndarr . shape == other . _ndarr . shape
36
+ (adds `PyArray_Descr::flags` like `NPY_ITEM_HASOBJECT` and `NPY_NEEDS_INIT`)
37
+ """
38
+ return self .dtype . hasobject
68
39
69
- def __repr__ (self ) -> str :
70
- return f'{ type (self ).__name__ } (id={ hex (id (self ))} , shape={ self ._ndarr .shape } , dtype={ self ._ndarr .dtype } )'
40
+ def is_compatible (self , other : 'ScalarType' ) -> bool :
41
+ """Check if both objects are compatible for set/cast operations"""
42
+ return self .shape == other .shape
43
+
44
+ def _create (self ) -> 'Scalar' :
45
+ """Instantiate Scalar object, that includes actual data storage"""
46
+ return Scalar (shape = self .shape , dtype = self .dtype )
71
47
72
48
def _get_np_view (self , dataptr : int ) -> np .ndarray :
73
49
"""Create numpy array that uses `dataptr` memory block"""
74
50
ct_array = ctypes .cast (
75
- dataptr , ctypes .POINTER (custom_as_ctypes_type (self ._ndarr . dtype ))
51
+ dataptr , ctypes .POINTER (custom_as_ctypes_type (self .dtype ))
76
52
)
77
- return np .ctypeslib .as_array (ct_array , shape = self ._ndarr . shape )
53
+ return np .ctypeslib .as_array (ct_array , shape = self .shape )
78
54
79
55
def _get_strided_np_view (
80
56
self , dataptr : int , size : int , stride : int
81
57
) -> np .ndarray :
82
58
"""Create numpy array that uses strided `dataptr` memory block"""
83
59
# Start with ctypes object to cover single SampleScalar
84
- ct_base = custom_as_ctypes_type (self ._ndarr . dtype )
85
- for dim in reversed (self ._ndarr . shape ):
60
+ ct_base = custom_as_ctypes_type (self .dtype )
61
+ for dim in reversed (self .shape ):
86
62
ct_base = ct_base * dim
87
63
88
64
# Single ctypes object to cover whole strided data-block
@@ -101,23 +77,27 @@ class PaddedStruct(ctypes.Structure):
101
77
ct_array = ctypes .cast (dataptr , ctypes .POINTER (ct_base ))
102
78
return np .ctypeslib .as_array (ct_array , shape = ())
103
79
104
- def setitem (self , src : 'SampleScalar ' , dataptr : int ) -> None :
80
+ def setitem (self , src : 'Scalar ' , dataptr : int ) -> int :
105
81
"""Python `NPY_DT_setitem` implementation
106
82
107
83
NOTE:
108
84
`dataptr` is a buffer address, valid during execution of the function only
109
85
"""
110
- if not self .is_compatible (src ):
86
+ if not src .is_compatible (self ):
111
87
raise ValueError ('Incompatible item value' )
112
88
89
+ # TODO: Avoid private member access
113
90
self ._get_np_view (dataptr )[...] = src ._ndarr
91
+ return 0 # -1 on failure
114
92
115
- def getitem (self , dataptr : int ) -> 'SampleScalar ' :
93
+ def getitem (self , dataptr : int ) -> 'Scalar ' :
116
94
"""Python `NPY_DT_getitem` implementation
117
95
118
- NOTE: See setitem()
96
+ NOTE:
97
+ `dataptr` is a buffer address, valid during execution of the function only
119
98
"""
120
- new = self .copy ()
99
+ new = self ._create ()
100
+ # TODO: Avoid private member access
121
101
new ._ndarr [...] = self ._get_np_view (dataptr )
122
102
return new
123
103
@@ -126,14 +106,13 @@ def clear_loop(self, data: int, size: int, stride: int) -> int:
126
106
127
107
NOTE: See setitem()
128
108
"""
129
- print (f'scalar.py: SampleScalar.clear_loop { data = } , { size = } , { stride = } ' )
130
109
view = self ._get_strided_np_view (data , size , stride )
131
110
view [...] = 0 # Force dereference of all object-entries
132
111
return 0
133
112
134
113
def cast_loop (
135
114
self ,
136
- other : 'SampleScalar ' ,
115
+ other : 'ScalarType ' ,
137
116
data : tuple [int , int ],
138
117
dimensions : tuple [int ],
139
118
strides : tuple [int , int ],
@@ -142,10 +121,47 @@ def cast_loop(
142
121
143
122
NOTE: See setitem()
144
123
"""
145
- print (
146
- f'scalar.py: SampleScalar.cast_loop { other = } , { data = } , { dimensions = } , { strides = } '
147
- )
148
124
in_view = other ._get_strided_np_view (data [0 ], dimensions [0 ], strides [0 ])
149
125
out_view = self ._get_strided_np_view (data [1 ], dimensions [0 ], strides [1 ])
150
126
out_view [...] = in_view
151
127
return 0
128
+
129
+
130
+ class Scalar (ScalarType ):
131
+ _ndarr : np .ndarray
132
+
133
+ def __init__ (self , * args , ** kwargs ):
134
+ super ().__init__ (* args , ** kwargs )
135
+ self ._ndarr = np .empty (self .shape , dtype = self .dtype )
136
+
137
+
138
+ def custom_as_ctypes_type (dtype : np .dtype ):
139
+ """Wrapper around `numpy.ctypeslib.as_ctypes_type` that maps 'O' to py_object"""
140
+ if not dtype .hasobject :
141
+ return np .ctypeslib .as_ctypes_type (dtype )
142
+
143
+ if dtype .fields is None :
144
+ # Simple dtype or array
145
+ if dtype .subdtype :
146
+ # Handle sub-array types like ('O', 3)
147
+ base , shape = dtype .subdtype
148
+ new_base = custom_as_ctypes_type (base )
149
+ for dim in reversed (shape ):
150
+ new_base = new_base * dim
151
+ return new_base
152
+ else :
153
+ # Simple object (non-objects are already handled)
154
+ assert dtype == np .dtype (object )
155
+ return ctypes .py_object
156
+
157
+ # Structured dtype
158
+ new_fields = []
159
+ for name , (field_dtype , * _ ) in dtype .fields .items ():
160
+ new_field_dtype = custom_as_ctypes_type (field_dtype )
161
+ new_fields .append ((name , new_field_dtype ))
162
+
163
+ class CustomStruct (ctypes .Structure ):
164
+ _pack_ = dtype .alignment
165
+ _fields_ = new_fields
166
+
167
+ return CustomStruct
0 commit comments