了解切片符号

我需要一个关于 Python 切片符号的很好的解释(引用是一个加号)。

对我而言,此表示法需要一些注意。

它看起来非常强大,但是我还没有完全了解它。

答案

真的很简单:

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

还有step值,可以与以上任何一个一起使用:

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

要记住的关键点是:stop值表示不在所选切片中的第一个值。因此, stopstart之间的差异是所选元素的数量(如果step为 1,则为默认值)。

另一个功能是startstop可以是负数 ,这意味着它是从数组的末尾而不是开头开始计数。所以:

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

同样, step可以为负数:

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 对程序员很友好。例如,如果您要求a[:-2]并且a仅包含一个元素,则会得到一个空列表而不是错误。有时您会更喜欢该错误,因此您必须意识到这种情况可能会发生。

slice()对象的关系

在上面的代码中,实际上使用:表示法将切片运算符[]slice()对象一起使用(仅在[]有效),即:

a[start:stop:step]

等效于:

a[slice(start, stop, step)]

切片对象的行为也取决于参数的数量,与range()略有不同,即,都支持slice(stop)slice(start, stop[, step]) 。要跳过指定给定参数的操作,可以使用None ,例如a[start:]等同于a[slice(start, None)]a[::-1]等同于a[slice(None, None, -1)]

尽管基于:的符号对于简单切片非常有用,但是对slice()对象的显式使用简化了切片的程序化生成。

Python 教程对此进行了讨论(向下滚动直到您了解有关切片的内容)。

ASCII 艺术图也有助于记住切片的工作方式:

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

记住切片如何工作的一种方式是将索引视为指向字符之间的指针,第一个字符的左边缘编号为 0。然后, n 个字符的字符串的最后一个字符的右侧边缘具有索引n

列举语法允许的可能性:

>>> seq[:]                # [seq[0],   seq[1],          ..., seq[-1]    ]
>>> seq[low:]             # [seq[low], seq[low+1],      ..., seq[-1]    ]
>>> seq[:high]            # [seq[0],   seq[1],          ..., seq[high-1]]
>>> seq[low:high]         # [seq[low], seq[low+1],      ..., seq[high-1]]
>>> seq[::stride]         # [seq[0],   seq[stride],     ..., seq[-1]    ]
>>> seq[low::stride]      # [seq[low], seq[low+stride], ..., seq[-1]    ]
>>> seq[:high:stride]     # [seq[0],   seq[stride],     ..., seq[high-1]]
>>> seq[low:high:stride]  # [seq[low], seq[low+stride], ..., seq[high-1]]

当然,如果(high-low)%stride != 0 ,则终点将略低于high-1

如果stride为负,则由于我们递减计数,因此顺序有所更改:

>>> seq[::-stride]        # [seq[-1],   seq[-1-stride],   ..., seq[0]    ]
>>> seq[high::-stride]    # [seq[high], seq[high-stride], ..., seq[0]    ]
>>> seq[:low:-stride]     # [seq[-1],   seq[-1-stride],   ..., seq[low+1]]
>>> seq[high:low:-stride] # [seq[high], seq[high-stride], ..., seq[low+1]]

扩展切片(带有逗号和省略号)通常仅由特殊的数据结构(如 NumPy)使用;基本序列不支持它们。

>>> class slicee:
...     def __getitem__(self, item):
...         return repr(item)
...
>>> slicee()[0, 1:2, ::5, ...]
'(0, slice(1, 2, None), slice(None, None, 5), Ellipsis)'

上面的答案不讨论切片分配。要了解切片分配,在 ASCII 艺术中添加另一个概念很有帮助:

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

>>> p = ['P','y','t','h','o','n']
# Why the two sets of numbers:
# indexing gives items, not lists
>>> p[0]
 'P'
>>> p[5]
 'n'

# Slicing gives lists
>>> p[0:1]
 ['P']
>>> p[0:2]
 ['P','y']

对于从零到 n 的切片,一种试探法是:“零是起点,从起点开始,并在列表中取 n 个项目”。

>>> p[5] # the last of six items, indexed from zero
 'n'
>>> p[0:5] # does NOT include the last item!
 ['P','y','t','h','o']
>>> p[0:6] # not p[0:5]!!!
 ['P','y','t','h','o','n']

另一个启发式方法是:“对于任何切片,将起始位置替换为零,应用先前的启发式方法以获取列表的末尾,然后将第一个数字计算回去以从开始处切掉项”

>>> p[0:4] # Start at the beginning and count out 4 items
 ['P','y','t','h']
>>> p[1:4] # Take one item off the front
 ['y','t','h']
>>> p[2:4] # Take two items off the front
 ['t','h']
# etc.

切片分配的第一个规则是,由于切片返回一个列表,因此切片分配需要一个列表(或其他可迭代的):

>>> p[2:3]
 ['t']
>>> p[2:3] = ['T']
>>> p
 ['P','y','T','h','o','n']
>>> p[2:3] = 't'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable

切片分配的第二条规则(您也可以在上方看到)是,列表的任何部分都是通过切片索引返回的,这与切片分配更改的部分相同:

>>> p[2:4]
 ['T','h']
>>> p[2:4] = ['t','r']
>>> p
 ['P','y','t','r','o','n']

切片分配的第三个规则是,分配的列表(可迭代)不必具有相同的长度。索引切片被简单地切出,并被分配的内容全部替换:

>>> p = ['P','y','t','h','o','n'] # Start over
>>> p[2:4] = ['s','p','a','m']
>>> p
 ['P','y','s','p','a','m','o','n']

习惯最棘手的部分是分配给空片。使用启发式 1 和 2 可以很容易地索引一个空片:

>>> p = ['P','y','t','h','o','n']
>>> p[0:4]
 ['P','y','t','h']
>>> p[1:4]
 ['y','t','h']
>>> p[2:4]
 ['t','h']
>>> p[3:4]
 ['h']
>>> p[4:4]
 []

然后,一旦您看到了,将切片分配给空切片也是有意义的:

>>> p = ['P','y','t','h','o','n']
>>> p[2:4] = ['x','y'] # Assigned list is same length as slice
>>> p
 ['P','y','x','y','o','n'] # Result is same length
>>> p = ['P','y','t','h','o','n']
>>> p[3:4] = ['x','y'] # Assigned list is longer than slice
>>> p
 ['P','y','t','x','y','o','n'] # The result is longer
>>> p = ['P','y','t','h','o','n']
>>> p[4:4] = ['x','y']
>>> p
 ['P','y','t','h','x','y','o','n'] # The result is longer still

请注意,由于我们没有更改分片的第二个数字(4),因此即使我们分配给空分片,插入的项目也总是紧靠'o' 堆积。因此,空切片分配的位置是非空切片分配的位置的逻辑扩展。

进行一点备份,当您继续我们从头开始计算切片的过程时,会发生什么?

>>> p = ['P','y','t','h','o','n']
>>> p[0:4]
 ['P','y','t','h']
>>> p[1:4]
 ['y','t','h']
>>> p[2:4]
 ['t','h']
>>> p[3:4]
 ['h']
>>> p[4:4]
 []
>>> p[5:4]
 []
>>> p[6:4]
 []

使用切片,一旦完成,就完成了;它不会开始向后切片。在 Python 中,除非您通过使用负数明确要求它们,否则您不会获得负面的进步。

>>> p[5:3:-1]
 ['n','o']

“一旦完成,就完成了” 规则有一些奇怪的后果:

>>> p[4:4]
 []
>>> p[5:4]
 []
>>> p[6:4]
 []
>>> p[6]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

实际上,与索引相比,Python 切片非常防错:

>>> p[100:200]
 []
>>> p[int(2e99):int(1e99)]
 []

有时这会派上用场,但也会导致一些奇怪的行为:

>>> p
 ['P', 'y', 't', 'h', 'o', 'n']
>>> p[int(2e99):int(1e99)] = ['p','o','w','e','r']
>>> p
 ['P', 'y', 't', 'h', 'o', 'n', 'p', 'o', 'w', 'e', 'r']

根据您的应用程序,可能…… 或可能不是…… 您所希望的!


以下是我的原始答案的文字。它对很多人都有用,所以我不想删除它。

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

这也可以澄清切片和索引之间的区别。

解释 Python 的切片符号

总之,冒号( : )中下标符号( subscriptable[subscriptarg]化妆切片符号 - 其具有可选的参数, startstopstep

sliceable[start:stop:step]

Python 切片是一种计算快速的方法,可以有条不紊地访问部分数据。我认为,即使是一名中级 Python 程序员,这也是该语言必须熟悉的一个方面。

重要定义

首先,让我们定义一些术语:

start:切片的开始索引,它将包含此索引处的元素,除非它与stop相同,默认为 0,即第一个索引。如果为负,则意味着从末尾开始n项目。

stop:切片的结束索引, 包含该索引处的元素,默认为要切片的序列的长度,即直至并包括结束。

步骤:索引增加的数量,默认为 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]

并记得startstopstep有默认值,因此要访问默认值,只需省略参数。

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

my_list[-9:]

看到此内容时,我将括号中的部分读为 “从末尾到第 9 位”。 (实际上,我在心理上将其缩写为 “-9,on”)

说明:

完整的符号是

my_list[-9:None:None]

并替换为默认值(实际上,当step为负时, stop的默认值为-len(my_list) - 1 ,因此 stop 的None实际上仅意味着它将转到执行此步骤的最后一个步骤):

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

冒号 :告诉 Python 您在给它一个分片而不是一个常规索引。这就是为什么在 Python 2 中制作列表的浅表副本的惯用方式是

list_copy = sequence[:]

清除它们的方法是:

del my_list[:]

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

如果step为负,则startstop更改的默认值

默认情况下,当step参数为空(或None )时,会将其分配给+1

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

因此,负片将更改startstop的默认值!

在来源中确认

我希望鼓励用户阅读源代码和文档。 切片对象源代码和此逻辑可在此处找到 。首先,我们确定step是否为负:

step_is_negative = step_sign < 0;

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

if (step_is_negative) {
    lower = PyLong_FromLong(-1L);
    if (lower == NULL)
        goto error;

    upper = PyNumber_Add(length, lower);
    if (upper == NULL)
        goto error;
}

否则, step为正,下界将为零,上限(我们将达到但不包括在内)的是切片列表的长度。

else {
    lower = _PyLong_Zero;
    Py_INCREF(lower);
    upper = length;
    Py_INCREF(upper);
}

然后,我们可能需要申请默认startstop - 默认则start被作为上限来计算,当step是否定的:

if (self->start == Py_None) {
    start = step_is_negative ? upper : lower;
    Py_INCREF(start);
}

stop ,下限:

if (self->stop == Py_None) {
    stop = step_is_negative ? lower : upper;
    Py_INCREF(stop);
}

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

您可能会发现,将形成切片与将切片传递到list.__getitem__方法分开是很有用的( 这就是方括号所做的事情 )。即使您不是新手,它也可以使您的代码更具可读性,以便其他可能需要阅读代码的人可以更轻松地了解您的操作。

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

last_nine_slice = slice(-9, None)

第二个参数None是必需的,因此第一个参数将被解释为start参数, 否则将成为stop参数

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

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

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

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

内存注意事项:

由于 Python 列表切片在内存中创建了新对象,因此需要注意的另一个重要功能是itertools.islice 。通常,您需要遍历一个切片,而不仅仅是在内存中静态创建它。 islice非常适合此。请注意,它不支持startstopstep否定参数,因此,如果这是一个问题,则可能需要预先计算索引或将可迭代对象反转。

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]

列表切片可以复制的事实是列表本身的功能。如果要切片高级对象(例如 Pandas DataFrame),则它可能会返回原始视图,而不是副本。

当我第一次看到切片语法时,有几件事对我来说并不立即显而易见:

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

在 Python 2.7 中

用 Python 切片

[a:b:c]

len = length of string, tuple or list

c -- default is +1. The sign of c indicates forward or backward, absolute value of c indicates steps. Default is forward with step size 1. Positive means forward, negative means backward.

a --  When c is positive or blank, default is 0. When c is negative, default is -1.

b --  When c is positive or blank, default is len. When c is negative, default is -(len+1).

了解索引分配非常重要。

In forward direction, starts at 0 and ends at len-1

In backward direction, starts at -1 and ends at -len

当您说 [a:b:c] 时,您要说的是根据 c 的符号(向前或向后),从 a 开始并在 b 结束(不包括位于 bth 索引处的元素)。使用上面的索引规则,请记住,您只会在此范围内找到元素:

-len, -len+1, -len+2, ..., 0, 1, 2,3,4 , len -1

但是这个范围在两个方向上都可以无限地继续:

...,-len -2 ,-len-1,-len, -len+1, -len+2, ..., 0, 1, 2,3,4 , len -1, len, len +1, len+2 , ....

例如:

0    1    2   3    4   5   6   7   8   9   10   11
             a    s    t   r    i   n   g
    -9  -8  -7   -6   -5  -4   -3  -2  -1

如果您对 a,b 和 c 的选择允许您在使用上方 a,b,c 的规则遍历时与上述范围重叠,则您将获得一个包含元素的列表(在遍历期间被触摸)或一个空列表。

最后一件事:如果 a 和 b 相等,那么您还会得到一个空列表:

>>> l1
[2, 3, 4]

>>> l1[:]
[2, 3, 4]

>>> l1[::-1] # a default is -1 , b default is -(len+1)
[4, 3, 2]

>>> l1[:-4:-1] # a default is -1
[4, 3, 2]

>>> l1[:-3:-1] # a default is -1
[4, 3]

>>> l1[::] # c default is +1, so a default is 0, b default is len
[2, 3, 4]

>>> l1[::-1] # c is -1 , so a default is -1 and b default is -(len+1)
[4, 3, 2]


>>> l1[-100:-200:-1] # Interesting
[]

>>> l1[-1:-200:-1] # Interesting
[4, 3, 2]


>>> l1[-1:-1:1]
[]


>>> l1[-1:5:1] # Interesting
[4]


>>> l1[1:-7:1]
[]

>>> l1[1:-7:-1] # Interesting
[3, 2]

>>> l1[:-2:-2] # a default is -1, stop(b) at -2 , step(c) by 2 in reverse direction
[4]

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)

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

(from:to:step)

它们都是可选的:

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

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

无论如何这对我有用...

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

首先了解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

从开始start的,增量step ,没有达到stop 。很简单的。

关于负步长要记住的一点是, stop始终是排除的端点,无论该端点是更高还是更低。如果要以相反的顺序切相同的片,则分开进行反转会更清洁:例如'abcde'[1:-2][::-1]从左边切一个字符,从右边切两个字符,然后反转。 (另请参见reversed() 。)

序列切片相同,不同之处在于它首先对负索引进行规范化,并且永远不会超出序列范围:

待办事项 :当 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]

不必担心 “ is None详细信息 - 请记住,省略start和 / 或stop总是可以正确地完成整个过程。

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