【问题标题】:Generally slicing just the one element of a list/tuple?通常只切片列表/元组的一个元素?
【发布时间】:2014-07-31 02:29:59
【问题描述】:

这个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__ 返回的内容太短有点奇怪(危险)。

问题:

  1. “近乎包裹”的切片较短,而不是(可能)引发IndexError: (list/string/tuple) index out of range" exception 是否有特定的设计原因?

  2. 如何创建列表的子类来管理它?

  3. 这在 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'

【问题讨论】:

  • 这是来自 python 邮件列表的thread,讨论您的问题和其他相关实施细节。

标签: python linked-list subclassing slice


【解决方案1】:

负索引从列表的末尾倒数,但如果它们足够大,它们可能会超过开头。当它们超出时,它们只是固定到列表中的第一项。

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 子类化来执行此操作,但对我来说,使用此配方或仅检查空切片似乎更容易。

【讨论】:

    【解决方案2】:

    有时可以使用“MixIn”类修补元组和列表包装问题(超过“[-1:0]”边界)。

    例如代码:

    #!/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']
    

    注意:字符串类型不能被子类化,因此不能被修补。
    另外:使用负 start:stop 参数调用成员 __getslice__ 和 __setslice__ 很棘手,因为这些参数会被解释器“摆弄”......因此需要额外的补丁代码......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-15
      • 2019-11-23
      • 1970-01-01
      • 1970-01-01
      • 2017-02-26
      • 1970-01-01
      • 1970-01-01
      • 2021-05-15
      相关资源
      最近更新 更多