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


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


当前回答

使用一点后,我意识到最简单的描述是它与for循环中的参数完全相同。。。

(from:to:step)

其中任何一项都是可选的:

(:to:step)
(from::step)
(from:to)

然后,负索引只需要将字符串的长度添加到负索引中即可理解它。

不管怎样,这对我来说都很有效。。。

其他回答

语法为:

a[start:stop]  # items start through stop-1
a[start:]      # items start through the rest of the array
a[:stop]       # items from the beginning through stop-1
a[:]           # a copy of the whole array

还有一个步长值,可用于上述任何一项:

a[start:stop:step] # start through not past stop, by step

要记住的关键点是:stop值表示不在所选切片中的第一个值。因此,停止和开始之间的区别是所选元素的数量(如果步骤为1,则为默认值)。

另一个特点是start或stop可以是负数,这意味着它从数组的末尾开始计数,而不是从开始计数。因此:

a[-1]    # last item in the array
a[-2:]   # last two items in the array
a[:-2]   # everything except the last two items

类似地,步骤可以是负数:

a[::-1]    # all items in the array, reversed
a[1::-1]   # the first two items, reversed
a[:-3:-1]  # the last two items, reversed
a[-3::-1]  # everything except the last two items, reversed

如果项目比你要求的少,Python对程序员很友好。例如,如果您请求一个[:-2],而一个只包含一个元素,则会得到一个空列表而不是一个错误。有时你会更喜欢错误,所以你必须意识到这可能会发生。

与切片对象的关系

切片对象可以表示切片操作,即:

a[start:stop:step]

相当于:

a[slice(start, stop, step)]

根据参数的数量,切片对象的行为也略有不同,类似于range(),即切片(stop)和切片(start,stop[,step])都受支持。要跳过指定给定参数,可以使用None,例如[start:]等同于[sslice(start,None)]或[::-1]等同于[Sslice(None,None,-1)]。

虽然基于:的表示法对简单切片非常有用,但slice()对象的显式使用简化了切片的编程生成。

通常,使用大量硬编码索引值编写代码会提高可读性以及维护混乱。例如,如果一年后你回到代码,你会看看它,想知道你写的时候在想什么只是一种更清楚地说明代码实际在做什么的方式。通常,内置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.>>>

这是我教新手切片的方法:

理解索引和切片之间的区别:

WikiPython有一幅惊人的图片,它清楚地区分了索引和切片。

这是一个包含六个元素的列表。为了更好地理解切片,请将该列表视为一组放在一起的六个框。每个盒子里都有一个字母表。

索引就像处理盒子的内容。您可以检查任何框的内容。但是你不能同时检查多个盒子的内容。你甚至可以替换盒子里的东西。但你不能在一个盒子里放两个球,也不能一次换两个球。

In [122]: alpha = ['a', 'b', 'c', 'd', 'e', 'f']

In [123]: alpha
Out[123]: ['a', 'b', 'c', 'd', 'e', 'f']

In [124]: alpha[0]
Out[124]: 'a'

In [127]: alpha[0] = 'A'

In [128]: alpha
Out[128]: ['A', 'b', 'c', 'd', 'e', 'f']

In [129]: alpha[0,1]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-129-c7eb16585371> in <module>()
----> 1 alpha[0,1]

TypeError: list indices must be integers, not tuple

切片就像处理盒子一样。你可以拿起第一个盒子放在另一张桌子上。要拿起盒子,你只需要知道盒子的开始和结束位置。

您甚至可以选择前三个框或最后两个框,或1到4之间的所有框。所以,如果你知道开始和结束,你可以选择任何一组框。这些位置称为开始和停止位置。

有趣的是,您可以同时替换多个框。此外,您可以在任何地方放置多个盒子。

In [130]: alpha[0:1]
Out[130]: ['A']

In [131]: alpha[0:1] = 'a'

In [132]: alpha
Out[132]: ['a', 'b', 'c', 'd', 'e', 'f']

In [133]: alpha[0:2] = ['A', 'B']

In [134]: alpha
Out[134]: ['A', 'B', 'c', 'd', 'e', 'f']

In [135]: alpha[2:2] = ['x', 'xx']

In [136]: alpha
Out[136]: ['A', 'B', 'x', 'xx', 'c', 'd', 'e', 'f']

切片步骤:

到目前为止,您已连续拾取箱子。但有时你需要单独拾取。例如,您可以每隔一秒钟拾取一个盒子。你甚至可以从最后每隔三个盒子取一个。该值称为步长。这代表了您连续拾取之间的差距。如果您从开始到结束拾取框,则步长应为正值,反之亦然。

In [137]: alpha = ['a', 'b', 'c', 'd', 'e', 'f']

In [142]: alpha[1:5:2]
Out[142]: ['b', 'd']

In [143]: alpha[-1:-5:-2]
Out[143]: ['f', 'd']

In [144]: alpha[1:5:-2]
Out[144]: []

In [145]: alpha[-1:-5:2]
Out[145]: []

Python如何找出缺少的参数:

切片时,如果忽略了任何参数,Python会尝试自动计算。

如果您检查CPython的源代码,您会发现一个名为PySlice_GetIndices Ex()的函数,它计算出任何给定参数的切片索引。下面是Python中的逻辑等价代码。

此函数采用Python对象和可选参数进行切片,并返回所请求切片的开始、停止、步骤和切片长度。

def py_slice_get_indices_ex(obj, start=None, stop=None, step=None):

    length = len(obj)

    if step is None:
        step = 1
    if step == 0:
        raise Exception("Step cannot be zero.")

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

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

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

    return (start, stop, step, slice_length)

这就是切片背后的智慧。由于Python有一个名为slice的内置函数,您可以传递一些参数,并检查它如何巧妙地计算缺少的参数。

In [21]: alpha = ['a', 'b', 'c', 'd', 'e', 'f']

In [22]: s = slice(None, None, None)

In [23]: s
Out[23]: slice(None, None, None)

In [24]: s.indices(len(alpha))
Out[24]: (0, 6, 1)

In [25]: range(*s.indices(len(alpha)))
Out[25]: [0, 1, 2, 3, 4, 5]

In [26]: s = slice(None, None, -1)

In [27]: range(*s.indices(len(alpha)))
Out[27]: [5, 4, 3, 2, 1, 0]

In [28]: s = slice(None, 3, -1)

In [29]: range(*s.indices(len(alpha)))
Out[29]: [5, 4]

注:这篇文章最初写在我的博客《Python切片背后的智能》中。

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

解释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这样的高级对象,它可能会返回原始对象的视图,而不是副本。