Python的切片表示法是如何工作的?也就是说:当我编写[x:y:z]、a[:]、a]::2]等代码时,我如何理解哪些元素最终会出现在切片中?请在适当的地方附上参考资料。


另请参见:为什么切片和范围上限是互斥的?


当前回答

我有点沮丧,因为找不到一个准确描述切片功能的在线源代码或Python文档。

我接受了Aaron Hall的建议,阅读了CPython源代码的相关部分,并编写了一些Python代码,这些代码执行切片与CPython中的切片类似。我已经用Python 3对整数列表进行了数百万次随机测试。

您可能会发现我的代码中对CPython中相关函数的引用很有用。

def slicer(x, start=None, stop=None, step=None):
    """ Return the result of slicing list x.  

    See the part of list_subscript() in listobject.c that pertains 
    to when the indexing item is a PySliceObject.
    """

    # Handle slicing index values of None, and a step value of 0.
    # See PySlice_Unpack() in sliceobject.c, which
    # extracts start, stop, step from a PySliceObject.
    maxint = 10000000       # A hack to simulate PY_SSIZE_T_MAX
    if step is None:
        step = 1
    elif step == 0:
        raise ValueError('slice step cannot be zero')

    if start is None:
        start = maxint if step < 0 else 0
    if stop is None:
        stop = -maxint if step < 0 else maxint

    # Handle negative slice indexes and bad slice indexes.
    # Compute number of elements in the slice as slice_length.
    # See PySlice_AdjustIndices() in sliceobject.c
    length = len(x)
    slice_length = 0

    if start < 0:
        start += length
        if start < 0:
            start = -1 if step < 0 else 0
    elif start >= length:
        start = length - 1 if step < 0 else length

    if stop < 0:
        stop += length
        if stop < 0:
            stop = -1 if step < 0 else 0
    elif stop > length:
        stop = length - 1 if step < 0 else length

    if step < 0:
        if stop < start:
            slice_length = (start - stop - 1) // (-step) + 1
    else:
        if start < stop:
            slice_length = (stop - start - 1) // step + 1

    # Cases of step = 1 and step != 1 are treated separately
    if slice_length <= 0:
        return []
    elif step == 1:
        # See list_slice() in listobject.c
        result = []
        for i in range(stop - start):
            result.append(x[i+start])
        return result
    else:
        result = []
        cur = start
        for i in range(slice_length):
            result.append(x[cur])
            cur += step
        return result

其他回答

这里有一个简单的记忆方法,可以记住它是如何工作的:

S L*I*C*E*切片的“i”位于第一位,代表包容,“e”排在最后,代表独占。

所以array[j:k]将包括第j个元素,并排除第k个元素。

当我第一次看到切片语法时,有一些事情不是很明显:

>>> x = [1,2,3,4,5,6]
>>> x[::-1]
[6,5,4,3,2,1]

反转顺序的简单方法!

如果出于某种原因,您希望以相反的顺序进行每一项:

>>> x = [1,2,3,4,5,6]
>>> x[::-2]
[6,4,2]

如果你觉得切片中的负指数令人困惑,这里有一个非常简单的方法来考虑:用len-index替换负指数。例如,用len(list)-3替换-3。

说明切片在内部做什么的最佳方法是在实现此操作的代码中显示它:

def slice(list, start = None, end = None, step = 1):
  # Take care of missing start/end parameters
  start = 0 if start is None else start
  end = len(list) if end is None else end

  # Take care of negative start/end parameters
  start = len(list) + start if start < 0 else start
  end = len(list) + end if end < 0 else end

  # Now just execute a for-loop with start, end and step
  return [list[i] for i in range(start, end, step)]

还可以使用切片分配从列表中删除一个或多个元素:

r = [1, 'blah', 9, 8, 2, 3, 4]
>>> r[1:4] = []
>>> r
[1, 2, 3, 4]

我发现更容易记住它是如何工作的,然后我可以找出任何特定的开始/停止/步骤组合。

首先了解range()是很有启发性的:

def range(start=0, stop, step=1):  # Illegal syntax, but that's the effect
    i = start
    while (i < stop if step > 0 else i > stop):
        yield i
        i += step

从起点开始,一步一步递增,不要到达终点。非常简单。

关于消极步骤,需要记住的一点是,停止总是被排除的终点,无论它是高还是低。如果您希望相同的切片以相反的顺序进行,则单独进行反转会更为简单:例如,“abcde”[1:-2][::-1]从左侧切下一个字符,从右侧切下两个字符,然后反转。(另请参见reversed()。)

序列切片是相同的,只是它首先规范了负索引,并且它永远不能超出序列:

TODO:当abs(step)>1时,下面的代码出现了一个错误:“从不超出序列”;我认为我修补了它是正确的,但很难理解。

def this_is_how_slicing_works(seq, start=None, stop=None, step=1):
    if start is None:
        start = (0 if step > 0 else len(seq)-1)
    elif start < 0:
        start += len(seq)
    if not 0 <= start < len(seq):  # clip if still outside bounds
        start = (0 if step > 0 else len(seq)-1)
    if stop is None:
        stop = (len(seq) if step > 0 else -1)  # really -1, not last element
    elif stop < 0:
        stop += len(seq)
    for i in range(start, stop, step):
        if 0 <= i < len(seq):
            yield seq[i]

不要担心“无”的细节——只需记住,省略开始和/或停止总是正确的做法,以提供整个序列。

首先规范化负索引允许开始和/或停止从结尾独立计数:'abcde'[1:-2]=='abcde'[1:3]=='bc',尽管范围(1,-2)==[]。标准化有时被认为是“对长度取模”,但注意它只增加了一次长度:例如,“abcde”[-53:42]只是整个字符串。