一般如何只切片列表/元组中的一个元素?
这个 slice(-1,0)
是从一段实际的代码中提取出来的,乍一看像是一个切片的包装,但其实不是。
评论:我怀疑真正的“切片包装”会是另一个挑战。
对于“几乎包装”的切片,简单的解决办法是在这种特殊情况下省略上限:
AEIOU[-1:], AEIOU[-1:None], or (more quirkilly) AEIOU[-1:5]
更一般的尝试:(我使用的是:Python 2.6.6)
AEIOU[start%len(AEIOU): stop%len(AEIOU)] # simply struggles.
但是考虑到负数成员函数 __getitem__
总是有效,"几乎包装"的成员 __getslice__
直接返回一个太短的结果,这有点奇怪(也有点危险)。
问题:
有没有特定的设计原因导致“几乎包装”的切片会短,而不是(可能)抛出一个
IndexError: (list/string/tuple) index out of range" 异常
?我该如何创建一个列表的子类来处理这个问题?
在 Python 3.0 中,这个问题的处理方式有什么不同吗?
演示:
>>> AEIOU="AEIOU"
>>> for i in range(-len(),5,1): print "%-16s, %r, %r"%(slice(i,i+1,1),AEIOU[i],AEIOU[i:i+1])
...
slice(-5, -4, 1), 'A', 'A'
slice(-4, -3, 1), 'E', 'E'
slice(-3, -2, 1), 'I', 'I'
slice(-2, -1, 1), 'O', 'O'
slice(-1, 0, 1) , 'U', '' # missing U...
slice(0, 1, 1) , 'A', 'A'
slice(1, 2, 1) , 'E', 'E'
slice(2, 3, 1) , 'I', 'I'
slice(3, 4, 1) , 'O', 'O'
slice(4, 5, 1) , 'U', 'U'
我注意到一个普遍的“解决办法”是:
>>> i=-1; AEIOU[i:i%len(AEIOU)+1]
'U' # found U
错误更正,切片示例:
>>> s=3
>>> for i in range(-len(AEIOU),5,1): print "%-16s, %r, %r, %r"%(slice(i,i%len(AEIOU)+s,1),AEIOU[i],AEIOU[i:i+s], AEIOU[i:i%len(AEIOU)+s])
...
slice(-5, 3, 1) , 'A', 'AEI', 'AEI'
slice(-4, 4, 1) , 'E', 'EIO', 'EIO'
slice(-3, 5, 1) , 'I', '', 'IOU'
slice(-2, 6, 1) , 'O', '', 'OU'
slice(-1, 7, 1) , 'U', '', 'U'
slice(0, 3, 1) , 'A', 'AEI', 'AEI'
slice(1, 4, 1) , 'E', 'EIO', 'EIO'
slice(2, 5, 1) , 'I', 'IOU', 'IOU'
slice(3, 6, 1) , 'O', 'OU', 'OU'
slice(4, 7, 1) , 'U', 'U', 'U'
2 个回答
有时候,在处理元组和列表的边界问题(比如“[-1:0]”这种情况)时,可以通过一个“MixIn”类来解决。
例如:代码:
#!/usr/bin/env python
def wrapslice(*sl):
if len(sl)==1: sl=sl[0]
if isinstance(sl, int): return sl
else:
if not isinstance(sl,slice): sl=slice(*sl)
if None in (sl.start,sl.stop): return sl
elif sl.start>=0 or sl.stop<0: return sl
else: return slice(sl.start, None, sl.step)
class WrapMixin(object):
# Not sure how to use a decorator on a MixIn, so I use some "boilerplate" code...
def __getitem__(self,sl):
return super(WrapMixin,self).__getitem__(wrapslice(sl))
def __setitem__(self,sl, value):
return super(WrapMixin,self).__setitem__(wrapslice(sl), value)
def __delitem__(self,sl):
return super(WrapMixin,self).__delitem__(wrapslice(sl))
# Note: for __getslice__ [-1:2] converts to [-1+len:2]...
# i.e. add len to any -ve numbers! Ouch...
def __getslice__(self,start,stop):
if start>stop: stop+=len(self)
sl=wrapslice(start,stop)
return super(WrapMixin,self).__getslice__(sl.start, sl.stop)
def __setslice__(self,start,stop, value):
if start>stop: stop+=len(self)
sl=wrapslice(start,stop)
return super(WrapMixin,self).__setslice__(sl.start, sl.stop, value)
def __delslice__(self,start,stop):
if start>stop: stop+=len(self)
sl=wrapslice(start,stop)
return super(WrapMixin,self).__delslice__(sl.start, sl.stop)
# Todo: def append/insert/append/iadd/radd ...
#class WrapString(WrapMixin, string): pass # FAIL
class WrapTuple(WrapMixin, tuple): pass # GOOD
class WrapList(WrapMixin, list): pass # GOOD
if __name__=="__main__": # test case
aeiou=list("aeiou")
AEIOU=WrapList("AEIOU")
len_slice=3
for i in range(-5,5,1):
vanilla=slice(i,i+len_slice)
fixed=wrapslice(vanilla)
print AEIOU[i]+"...","Vanilla:",vanilla,"vs Patched:",fixed
print " get_slice VANILLA: %15r -> PATCHED: %15r"%(aeiou[vanilla.start:vanilla.stop], AEIOU[vanilla.start:vanilla.stop])
print " get_item VANILLA: %15r -> PATCHED: %15r"%(aeiou[vanilla], AEIOU[vanilla])
输出:
A... Vanilla: slice(-5, -2, None) vs Patched: slice(-5, -2, None)
get_slice VANILLA: ['a', 'e', 'i'] -> PATCHED: ['A', 'E', 'I']
get_item VANILLA: ['a', 'e', 'i'] -> PATCHED: ['A', 'E', 'I']
E... Vanilla: slice(-4, -1, None) vs Patched: slice(-4, -1, None)
get_slice VANILLA: ['e', 'i', 'o'] -> PATCHED: ['E', 'I', 'O']
get_item VANILLA: ['e', 'i', 'o'] -> PATCHED: ['E', 'I', 'O']
I... Vanilla: slice(-3, 0, None) vs Patched: slice(-3, None, None)
get_slice VANILLA: [] -> PATCHED: ['I', 'O', 'U']
get_item VANILLA: [] -> PATCHED: ['I', 'O', 'U']
O... Vanilla: slice(-2, 1, None) vs Patched: slice(-2, None, None)
get_slice VANILLA: [] -> PATCHED: ['O', 'U']
get_item VANILLA: [] -> PATCHED: ['O', 'U']
U... Vanilla: slice(-1, 2, None) vs Patched: slice(-1, None, None)
get_slice VANILLA: [] -> PATCHED: ['U'] <= Found U!
get_item VANILLA: [] -> PATCHED: ['U']
A... Vanilla: slice(0, 3, None) vs Patched: slice(0, 3, None)
get_slice VANILLA: ['a', 'e', 'i'] -> PATCHED: ['A', 'E', 'I']
get_item VANILLA: ['a', 'e', 'i'] -> PATCHED: ['A', 'E', 'I']
E... Vanilla: slice(1, 4, None) vs Patched: slice(1, 4, None)
get_slice VANILLA: ['e', 'i', 'o'] -> PATCHED: ['E', 'I', 'O']
get_item VANILLA: ['e', 'i', 'o'] -> PATCHED: ['E', 'I', 'O']
I... Vanilla: slice(2, 5, None) vs Patched: slice(2, 5, None)
get_slice VANILLA: ['i', 'o', 'u'] -> PATCHED: ['I', 'O', 'U']
get_item VANILLA: ['i', 'o', 'u'] -> PATCHED: ['I', 'O', 'U']
O... Vanilla: slice(3, 6, None) vs Patched: slice(3, 6, None)
get_slice VANILLA: ['o', 'u'] -> PATCHED: ['O', 'U']
get_item VANILLA: ['o', 'u'] -> PATCHED: ['O', 'U']
U... Vanilla: slice(4, 7, None) vs Patched: slice(4, 7, None)
get_slice VANILLA: ['u'] -> PATCHED: ['U']
get_item VANILLA: ['u'] -> PATCHED: ['U']
注意:字符串类型不能被继承,所以无法进行修补。
另外:使用负数作为起始和结束参数调用成员函数 __getslice__ 和 __setslice__ 时会比较复杂,因为这些参数会被解释器“调整”... 所以需要额外的修补代码...
负数索引是从列表的末尾开始计算的,但如果负数索引的绝对值太大,就会超过列表的开头。这时候,它会直接指向列表的第一个项目。
items = range(10)
items[-10:-9]
# 0
items[-20:-9]
# 0
正数索引是从列表的开头开始计算的。如果正数索引超过了列表的末尾,它也会直接指向列表的最后一个项目:
items[9:10]
#[9]
items[9:100]
#[9]
与普通的getitem访问不同,超出列表范围的索引会被限制在列表的末尾。通常,这样会返回一个空的切片:
items[99:100]
#[]
items[-99:-88]
#[]
在你的例子中,'aeiou'[-1:0]
和 'aeiou'[4:0]
是一样的——因为这是一个负长度但正步长的切片,所以它是空的。如果你用 'aeiou'[4:0:-1]
,你会得到 'uoie'
。
由于这种限制行为,返回的切片不一定会包含与指定范围之间的距离相同数量的项目。切片是一种很好的方式,可以在不事先检查边界的情况下获取项目,这正好与我认为你想要的相反:
stuff = ['a','b','c']
d = stuff[11:12]
# []
stuff[11]
#IndexError: list index out of range
如果你真的需要知道你的查询是否超出范围,你必须提前检查索引:
def strict_slice(the_list, begin, end):
listlen = len(the_list)
if begin > listlen or begin + listlen < 0:
raise IndexError, 'start index out of bounds'
if end + listlen < 0 or end > listlen:
raise IndexError, 'end index out of bounds'
return the_list[begin:end]
test = 'abcdefg'
strict_slice(test, 1, 3)
strict_slice(test, -10, 3)
# IndexError: start index out of bounds
strict_slice(test, 1, 20)
#IndexError: end index out of bounds
虽然你可以通过子类化 list
来实现这一点,但我觉得使用这个方法或者直接检查空切片更简单。