Python的切片表示法是如何工作的?也就是说:当我编写[x:y:z]、a[:]、a]::2]等代码时,我如何理解哪些元素最终会出现在切片中?请在适当的地方附上参考资料。
另请参见:为什么切片和范围上限是互斥的?
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
其他回答
前面的答案没有讨论使用著名的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”作用于第二维度。
我的大脑似乎很乐意接受lst[开始:结束]包含开始项。我甚至可以说这是一个“自然的假设”。
但偶尔会有一种怀疑悄悄出现,我的大脑会要求我保证它不包含结尾元素。
在这些时刻,我依靠这个简单的定理:
for any n, lst = lst[:n] + lst[n:]
这个漂亮的属性告诉我,lst[start:end]不包含end-th项,因为它位于lst[end:]中。
注意,这个定理对任何n都是正确的。例如,您可以检查
lst = range(10)
lst[:-42] + lst[-42:] == lst
返回True。
前面的大多数答案都解决了有关切片表示法的问题。
用于切片的扩展索引语法是aList[start:stop:step],基本示例如下:
:
更多切片示例:15个扩展切片
简单易懂:
在Python中,切片符号a[start:stop:step]可以用于从序列中选择一系列元素(例如列表、元组或字符串)。
起始索引是包括在切片中的第一个元素,
停止索引是从切片中排除的第一个元素,也是最后一个元素
步长值是切片元素之间的索引数。
例如,考虑以下列表:
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
如果要选择a的所有元素,可以使用切片符号a[:]:
>>> a[:]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
如果我们想选择a的所有元素,但跳过其他元素,我们可以使用切片符号a[::2]:
>>> a[::2]
[0, 2, 4, 6, 8]
如果我们想选择从第三个元素(索引2)到第七个元素(索引号6)的所有元素,我们可以使用切片符号a[2:7]:
>>> a[2:7]
[2, 3, 4, 5, 6]
如果我们想选择从第三个元素(索引2)到第七个元素(索引号6)的所有元素,但跳过其他元素,我们可以使用切片符号a[2:7:2]:
>>> a[2:7:2]
[2, 4, 6]
如果我们想选择从第三个元素(索引2)到列表末尾的所有元素,我们可以使用切片符号a[2:]:
>>> a[2:]
[2, 3, 4, 5, 6, 7, 8, 9]
如果我们想选择从列表开头到第七个元素(索引6)的所有元素,我们可以使用切片符号a[:7]:
>>> a[:7]
[0, 1, 2, 3, 4, 5, 6]
如果您想了解有关切片表示法的更多信息,可以参考Python官方文档:链接1链接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这样的高级对象,它可能会返回原始对象的视图,而不是副本。