Python的切片表示法是如何工作的?也就是说:当我编写[x:y:z]、a[:]、a]::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)]

其他回答

如果我们可以将切片与范围联系起来,这很容易理解,因为范围给出了索引。我们可以将切片分为以下两类:


1.无台阶或台阶>0。例如,[i:j]或[i:j:k](k>0)

假设序列为s=[1,2,3,4,5]。

如果0<i<len(s)和0<j<len,则[i:j:k]->范围(i,j,k)

例如,[0:3:2]->范围(0,3,2)->0,2

如果i>len或j>len,则i=len或j=len

例如,[0:100:2]->范围(0,len(s),2)->范围(0,5,2)->0,2,4

如果i<0或j<0,则i=max(0,len(s)+i)或j=max

例如,[0:-3:2]->范围(0,len(s)-3,2)->范围(0,2,2)->0

例如,[0:-1:2]->范围(0,len(s)-1,2)->范围(0,4,2)->0,2

如果未指定i,则i=0

例如,[:4:2]->范围(0,4,2)->范围(4,2)->0,2

如果未指定j,则j=len(s)

例如,[0::2]->范围(0,len(s),2)->范围(0,5,2)->0,2,4


2.步骤<0。例如,[i:j:k](k<0)

假设序列为s=[1,2,3,4,5]。

如果0<i<len(s)和0<j<len,则[i:j:k]->范围(i,j,k)

例如,[5:0:-2]->范围(5,0,-2)->5,3,1

如果i>len或j>len,则i=len(s)-1或j=len(s)-1

例如,[100:0:-2]->范围(len(s)-1,0,-2)->范围(4,0,-2)->4,2

如果i<0或j<0,则i=max(-1,len(s)+i)或j=max(-1len(s)+j)

例如,[-2:-10:-2]->range(len(s)-2,-1,-2)->range(3,-1,-1)->3,1

如果未指定i,则i=len(s)-1

例如,[:0:-2]->范围(len(s)-1,0,-2)->范围(4,0,-2)->4,2

如果未指定j,则j=-1

例如,[2::-2]->范围(2,-1,-2)->2,0

例如,[::-1]->range(len(s)-1,-1,-1)->range(4,-1,1)->4,3,2,1,0


总而言之

我有点沮丧,因为找不到一个准确描述切片功能的在线源代码或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

这只是为了一些额外的信息。。。考虑以下列表

>>> l=[12,23,345,456,67,7,945,467]

反转列表的其他几个技巧:

>>> l[len(l):-len(l)-1:-1]
[467, 945, 7, 67, 456, 345, 23, 12]

>>> l[:-len(l)-1:-1]
[467, 945, 7, 67, 456, 345, 23, 12]

>>> l[len(l)::-1]
[467, 945, 7, 67, 456, 345, 23, 12]

>>> l[::-1]
[467, 945, 7, 67, 456, 345, 23, 12]

>>> l[-1:-len(l)-1:-1]
[467, 945, 7, 67, 456, 345, 23, 12]

在Python中,最基本的切片形式如下:

l[start:end]

其中l是一些集合,start是一个包含索引,end是一个独占索引。

In [1]: l = list(range(10))

In [2]: l[:5] # First five elements
Out[2]: [0, 1, 2, 3, 4]

In [3]: l[-5:] # Last five elements
Out[3]: [5, 6, 7, 8, 9]

当从开始切片时,可以省略零索引,而当切片到结束时,可以忽略最终索引,因为它是冗余的,所以不要冗长:

In [5]: l[:3] == l[0:3]
Out[5]: True

In [6]: l[7:] == l[7:len(l)]
Out[6]: True

负整数在相对于集合结尾进行偏移时非常有用:

In [7]: l[:-1] # Include all elements but the last one
Out[7]: [0, 1, 2, 3, 4, 5, 6, 7, 8]

In [8]: l[-3:] # Take the last three elements
Out[8]: [7, 8, 9]

切片时可以提供超出范围的索引,例如:

In [9]: l[:20] # 20 is out of index bounds, and l[20] will raise an IndexError exception
Out[9]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [11]: l[-20:] # -20 is out of index bounds, and l[-20] will raise an IndexError exception
Out[11]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

请记住,分割集合的结果是一个全新的集合。此外,当在赋值中使用切片表示法时,切片赋值的长度不需要相同。将保留分配切片之前和之后的值,集合将收缩或增长以包含新值:

In [16]: l[2:6] = list('abc') # Assigning fewer elements than the ones contained in the sliced collection l[2:6]

In [17]: l
Out[17]: [0, 1, 'a', 'b', 'c', 6, 7, 8, 9]

In [18]: l[2:5] = list('hello') # Assigning more elements than the ones contained in the sliced collection l [2:5]

In [19]: l
Out[19]: [0, 1, 'h', 'e', 'l', 'l', 'o', 6, 7, 8, 9]

如果忽略开始索引和结束索引,则将创建集合的副本:

In [14]: l_copy = l[:]

In [15]: l == l_copy and l is not l_copy
Out[15]: True

如果在执行赋值操作时省略了开始和结束索引,则集合的整个内容将替换为引用内容的副本:

In [20]: l[:] = list('hello...')

In [21]: l
Out[21]: ['h', 'e', 'l', 'l', 'o', '.', '.', '.']

除了基本切片外,还可以应用以下符号:

l[start:end:step]

其中l是一个集合,start是一个包含索引,end是一个排他索引,step是一个步长,可以用来获取l中的每n个项目。

In [22]: l = list(range(10))

In [23]: l[::2] # Take the elements which indexes are even
Out[23]: [0, 2, 4, 6, 8]

In [24]: l[1::2] # Take the elements which indexes are odd
Out[24]: [1, 3, 5, 7, 9]

使用step提供了在Python中反转集合的有用技巧:

In [25]: l[::-1]
Out[25]: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

也可以使用负整数作为步骤,如下例所示:

In[28]:  l[::-2]
Out[28]: [9, 7, 5, 3, 1]

然而,使用负值作为步长可能会变得非常混乱。此外,为了成为Pythonic,您应该避免在单个切片中使用start、end和step。如果需要这样做,可以考虑在两个任务中完成(一个任务是切片,另一个任务则是跨步)。

In [29]: l = l[::2] # This step is for striding

In [30]: l
Out[30]: [0, 2, 4, 6, 8]

In [31]: l = l[1:-1] # This step is for slicing

In [32]: l
Out[32]: [2, 4, 6]

我不认为Python教程图(在各种其他答案中引用)是好的,因为这个建议适用于积极的步幅,但不适用于消极的步幅。

这是一个图表:

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1

从图中,我希望[-4,-6,-1]是yP,但它是ty。

>>> a = "Python"
>>> a[2:4:1] # as expected
'th'
>>> a[-4:-6:-1] # off by 1
'ty'

始终有效的方法是在字符或槽中思考,并将索引用作半开区间——如果是正步幅,则右开,如果是负步幅,那么左开。

这样,我可以将[-4:-6:-1]看作是区间术语中的(-6,-4])。

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
   0   1   2   3   4   5  
  -6  -5  -4  -3  -2  -1

 +---+---+---+---+---+---+---+---+---+---+---+---+
 | P | y | t | h | o | n | P | y | t | h | o | n |
 +---+---+---+---+---+---+---+---+---+---+---+---+
  -6  -5  -4  -3  -2  -1   0   1   2   3   4   5