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


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


当前回答

解释Python的切片表示法

简而言之,下标表示法(subscriptable[subscriptarg])中的冒号(:)构成切片表示法,它具有可选的参数start、stop和step:

sliceable[start:stop:step]

Python切片是一种快速计算的方法,可以系统地访问部分数据。在我看来,要成为一名中级Python程序员,这是语言的一个必须熟悉的方面。

重要定义

首先,让我们定义几个术语:

start:切片的起始索引,除非它与stop相同,否则它将包含该索引处的元素,默认为0,即第一个索引。如果是负数,则表示从末尾开始n个项目。stop:切片的结束索引,它不包括该索引处的元素,默认为切片序列的长度,即,直到并包括结束。step:索引增加的量,默认为1。如果它是负的,那么你正在反向切片可迭代的。

索引的工作原理

你可以做这些正数或负数中的任何一个。正数的含义很简单,但对于负数,就像Python中的索引一样,从开始和停止的末尾开始向后计数,对于步骤,只需减少索引。此示例来自文档的教程,但我对其进行了轻微修改,以指示每个索引引用的序列中的哪个项:

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

切片的工作原理

要对支持它的序列使用切片表示法,必须在序列后面的方括号中至少包含一个冒号(根据Python数据模型,这实际上实现了序列的__getitem_方法)

切片表示法的工作原理如下:

sequence[start:stop:step]

回想一下,start、stop和step都有默认值,所以要访问默认值,只需省略参数即可。

从列表(或任何其他支持它的序列,如字符串)中获取最后九个元素的切片表示法如下所示:

my_list[-9:]

当我看到这一点时,我把括号里的部分读成了“从结尾到结尾的第9个”(实际上,我在心里把它缩写为“-9,on”)

说明:

完整符号为

my_list[-9:None:None]

并替换默认值(实际上,当step为负值时,stop的默认值为-len(my_list)-1,因此None for stop实际上意味着它将转到任何结束步骤):

my_list[-9:len(my_list):1]

冒号:是告诉Python你给它一个切片,而不是一个常规索引。这就是为什么在Python2中制作列表浅拷贝的惯用方法是

list_copy = sequence[:]

清除它们的方法是:

del my_list[:]

(Python 3获得list.copy和list.clear方法。)

当步骤为负时,启动和停止的默认值将更改

默认情况下,当step参数为空(或None)时,将其赋值为+1。

但是您可以传入一个负整数,列表(或大多数其他标准可切片)将从结尾到开头进行切片。

因此,负切片将更改开始和停止的默认值!

在源中确认

我希望鼓励用户阅读源代码和文档。切片对象和此逻辑的源代码位于此处。首先,我们确定步骤是否为负:

step_is_negative=step_sign<0;

如果是这样的话,下限是-1,意味着我们一直切到并包括开头,上限是长度减1,意味着从结尾开始。(注意,这个-1的语义不同于用户可以在Python中传递指示最后一项的索引的-1。)

if(step_is_negative){lower=PyLong_FromLong(-1L);if(下限==NULL)转到错误;上限=PyNumber_Add(长度,下限);if(上限==NULL)转到错误;}

否则,step为正值,下限将为零,上限(我们将向上,但不包括)为切片列表的长度。

其他{lower=_PyLong_Zero;Py_INCREF(下部);上限=长度;Py_INCREF(上部);}

然后,我们可能需要应用start和stop的默认值。如果step为负值,那么start的默认值将计算为上限:

如果(self->start==Py_None){start=step_is_negative?上部:下部;Py_INCREF(启动);}

并停止,下限:

如果(self->stop==Py_None){stop=step_is_negative?下:上;Py_INCREF(停止);}

给你的切片起个描述性的名字!

您可能会发现将形成切片与将其传递到列表分开是很有用的__getitem_方法(这就是方括号的作用)。即使你不是新手,它也能让你的代码更可读,这样其他可能需要阅读你的代码的人就能更容易地理解你在做什么。

但是,不能只将一些用冒号分隔的整数分配给变量。您需要使用切片对象:

last_nine_slice = slice(-9, None)

第二个参数None是必需的,因此第一个参数被解释为开始参数,否则它将是停止参数。

然后可以将切片对象传递给序列:

>>> list(range(100))[last_nine_slice]
[91, 92, 93, 94, 95, 96, 97, 98, 99]

有趣的是,范围也可以分片:

>>> range(100)[last_nine_slice]
range(91, 100)

内存注意事项:

由于Python列表的切片会在内存中创建新的对象,因此需要注意的另一个重要函数是itertool.islice。通常,您需要对切片进行迭代,而不仅仅是在内存中静态创建。islice非常适合这个。需要注意的是,它不支持开始、停止或步骤的负参数,因此如果这是一个问题,您可能需要提前计算索引或反转可迭代项。

length = 100
last_nine_iter = itertools.islice(list(range(length)), length-9, None, 1)
list_last_nine = list(last_nine_iter)

现在:

>>> list_last_nine
[91, 92, 93, 94, 95, 96, 97, 98, 99]

列表切片复制是列表本身的一个特点。如果您正在切片像PandasDataFrame这样的高级对象,它可能会返回原始对象的视图,而不是副本。

其他回答

通常,使用大量硬编码索引值编写代码会提高可读性以及维护混乱。例如,如果一年后你回到代码,你会看看它,想知道你写的时候在想什么只是一种更清楚地说明代码实际在做什么的方式。通常,内置slice()创建一个切片对象,可以在切片的任何位置使用允许。例如:

>>> items = [0, 1, 2, 3, 4, 5, 6]
>>> a = slice(2, 4)
>>> items[2:4]
[2, 3]
>>> items[a]
[2, 3]
>>> items[a] = [10,11]
>>> items
[0, 1, 10, 11, 4, 5, 6]
>>> del items[a]
>>> items
[0, 1, 4, 5, 6]

如果您有一个切片实例,您可以通过查看它的s.start、s.stop和s.step属性。例如:

>>>a=切片(10、50、2)>>>a.启动10>>>a.停止50>>>a.台阶2.>>>

在找到这张很棒的桌子http://wiki.python.org/moin/MovingToPythonFromOtherLanguages

Python indexes and slices for a six-element list.
Indexes enumerate the elements, slices enumerate the spaces between the elements.

Index from rear:    -6  -5  -4  -3  -2  -1      a=[0,1,2,3,4,5]    a[1:]==[1,2,3,4,5]
Index from front:    0   1   2   3   4   5      len(a)==6          a[:5]==[0,1,2,3,4]
                   +---+---+---+---+---+---+    a[0]==0            a[:-2]==[0,1,2,3]
                   | a | b | c | d | e | f |    a[5]==5            a[1:2]==[1]
                   +---+---+---+---+---+---+    a[-1]==5           a[1:-1]==[1,2,3,4]
Slice from front:  :   1   2   3   4   5   :    a[-2]==4
Slice from rear:   :  -5  -4  -3  -2  -1   :
                                                b=a[:]
                                                b==[0,1,2,3,4,5] (shallow copy of a)

在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]

前面的答案没有讨论使用著名的NumPy包可以实现的多维数组切片:

切片也可以应用于多维数组。

# Here, a is a NumPy array

>>> a
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])
>>> a[:2, 0:3:2]
array([[1, 3],
       [5, 7]])

逗号前的“:2”作用于第一维度,逗号后的“0:3:2”作用于第二维度。

我自己使用“元素之间的索引点”方法来思考它,但描述它的一种方式有时有助于其他人获得它:

mylist[X:Y]

X是所需的第一个元素的索引。Y是不需要的第一个元素的索引。