预计阅读本页时间:-
Intercepting Slices
Interestingly, in addition to indexing, __getitem__ is also called for slice expressions. Formally speaking, built-in types handle slicing the same way. Here, for example, is slicing at work on a built-in list, using upper and lower bounds and a stride (see Chapter 7 if you need a refresher on slicing):
>>> L = [5, 6, 7, 8, 9]
>>> L[2:4] # Slice with slice syntax
[7, 8]
>>> L[1:]
[6, 7, 8, 9]
>>> L[:-1]
[5, 6, 7, 8]
>>> L[::2]
[5, 7, 9]
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
Really, though, slicing bounds are bundled up into a slice object and passed to the list’s implementation of indexing. In fact, you can always pass a slice object manually—slice syntax is mostly syntactic sugar for indexing with a slice object:
>>> L[slice(2, 4)] # Slice with slice objects
[7, 8]
>>> L[slice(1, None)]
[6, 7, 8, 9]
>>> L[slice(None, −1)]
[5, 6, 7, 8]
>>> L[slice(None, None, 2)]
[5, 7, 9]
This matters in classes with a __getitem__ method—the method will be called both for basic indexing (with an index) and for slicing (with a slice object). Our previous class won’t handle slicing because its math assumes integer indexes are passed, but the following class will. When called for indexing, the argument is an integer as before:
>>> class Indexer:
... data = [5, 6, 7, 8, 9]
... def __getitem__(self, index): # Called for index or slice
... print('getitem:', index)
... return self.data[index] # Perform index or slice
...
>>> X = Indexer()
>>> X[0] # Indexing sends __getitem__ an integer
getitem: 0
5
>>> X[1]
getitem: 1
6
>>> X[-1]
getitem: −1
9
When called for slicing, though, the method receives a slice object, which is simply passed along to the embedded list indexer in a new index expression:
>>> X[2:4] # Slicing sends __getitem__ a slice object
getitem: slice(2, 4, None)
[7, 8]
>>> X[1:]
getitem: slice(1, None, None)
[6, 7, 8, 9]
>>> X[:-1]
getitem: slice(None, −1, None)
[5, 6, 7, 8]
>>> X[::2]
getitem: slice(None, None, 2)
[5, 7, 9]
If used, the __setitem__ index assignment method similarly intercepts both index and slice assignments—it receives a slice object for the latter, which may be passed along in another index assignment in the same way:
def __setitem__(self, index, value): # Intercept index or slice assignment
...
self.data[index] = value # Assign index or slice
In fact, __getitem__ may be called automatically in even more contexts than indexing and slicing, as the next section explains.
Slicing and Indexing in Python 2.6
Prior to Python 3.0, classes could also define __getslice__ and __setslice__ methods to intercept slice fetches and assignments specifically; they were passed the bounds of the slice expression and were preferred over __getitem__ and __setitem__ for slices. These slice-specific methods have been removed in 3.0, so you should use __getitem__ and __setitem__ instead and allow for both indexes and slice objects as arguments. In most classes, this works without any special code, because indexing methods can manually pass along the slice object in the square brackets of another index expression (as in our example). See the section Membership: __contains__, __iter__, and __getitem__ for another example of slice interception at work.
Also, don’t confuse the (arguably unfortunately named) __index__ method in Python 3.0 for index interception; this method returns an integer value for an instance when needed and is used by built-ins that convert to digit strings:
>>> class C:
... def __index__(self):
... return 255
...
>>> X = C()
>>> hex(X) # Integer value
'0xff'
>>> bin(X)
'0b11111111'
>>> oct(X)
'0o377'
Although this method does not intercept instance indexing like __getitem__, it is also used in contexts that require an integer—including indexing:
>>> ('C' * 256)[255]
'C'
>>> ('C' * 256)[X] # As index (not X[i])
'C'
>>> ('C' * 256)[X:] # As index (not X[i:])
'C'
This method works the same way in Python 2.6, except that it is not called for the hex and oct built-in functions (use __hex__ and __oct__ in 2.6 instead to intercept these calls).