The getitem operator
This prototypes __getitem__ using the Python interface, introducing any fancy features we'd need to pull off that. If that won't work, we'll roll our own __cgetitem__ with an easier compilable interface, at least for now.
How Python getitem works
1 >>> class A:
2 ... def __getitem__(self, idx): print repr(idx)
3 ...
4 >>> a = A()
5 >>> a[2]
6 2
7 >>> a[2,3]
8 (2, 3)
9 >>> a[...,4]
10 (Ellipsis, 4)
11 >>> a[1:2]
12 slice(1, 2, None)
13 >>> a[1:2:-1, ..., 4]
14 (slice(1, 2, -1), Ellipsis, 4)
Prototype
Comment legend:
- ECTO - "Easily Compile-Time Optimizeable"; provided that the function is inlined.
- CTO - Possibly compile-time optimizeable with a not-too-complex visitor simulating a Python interpreter for compile-time-known expressions and statements. I.e., this comment below means that the values involved will be known at compile-time in the regular use-case (and that not optimizing is the right thing to do if it is not known)
- DBA - "Disappears Because of Assumptions".
I'll treat self.data always as the underlying C buffer rather than the Python attribute -- this issue is somewhat orthogonal, and anyway I'd guess that [] would lead to the C "overload" getting selected.
This is inside an cdef class ndarray block in a pxd file.
1 # Use "generic" (parameter polymorphism) in order to avoid certain typing issues
2 # that will be explained as we go. "index" will have to be compile-time optimized
3 # anyway, so can as well make it object.
4 #
5 # "self" is generic because in my parameter polymorphism draft, I propose that
6 # assumptions gets carried over when "generic" is used. I.e., using "generic"
7 # for self makes sure that one instance of the method is created per
8 # assumption-combination. (This is, I suppose, only really useful if the method
9 # is not inlined though. So might make it "object" and rely on inlining optimization
10 # instead *shrug*)
11
12 cdef generic __getitem__(generic self, object index):
13 if isinstance(index, int): # ECTO
14 if not self.ndim == 1: # DBA
15 return (<object>self)[index] # gets a slice
16 else:
17 # Somehow use an assumption, self.dtype, in a type context. If the dtype assumption
18 # is not made then this will raise compile-time error.
19 # Since the user is "probably" assigning this to a variable of the right type,
20 # the "generic" return value will be the correct one (or if not, the return statement
21 # will turn into raising the correct coercion error)
22 return (<self.dtype*>(self.data + self.strides[0] * index))[0]
23 else: # ECTO, is a tuple
24 # All of the below is CTO, but definitely the hardest part
25 offset = 0
26 for idx, item in enumerate(index):
27 if i.__class__ is slice or i is Ellipsis: break
28 else: offset += self.strides[idx] * item
29 else:
30 # can use direct access
31 return (<self.dtype*>(self.data + offset))[0]
32 # Encountered break, fall back to Python
33 return (<object>self)[index]
