Python:如何更简洁地使用带有间隔的切片语法?

16

假设我想从Python的列表中获取第一个元素,第3到200个元素以及步长为3的第201个元素到最后一个元素。

一种方法是使用不同的索引和连接:

new_list = old_list[0:1] + old_list[3:201] + old_list[201::3]

有没有办法只使用old_list上的一个索引来完成这个操作?我希望得到类似以下的结果(我知道这在语法上是不正确的,因为列表索引不能是列表,而且由于 Python 不幸地没有切片字面量,我只是在寻找接近的东西):

new_list = old_list[[0, 3:201, 201::3]]

我可以通过切换到NumPy数组来实现其中一些功能,但我更感兴趣的是如何在本地Python列表中实现。我也可以创建一个切片制造者或类似的东西,并可能将其强制转换为给我一个等效的切片对象以表示所有所需切片的组合。

但我正在寻找一些不涉及创建新类来管理切片的方法。我想只是将切片语法连接起来并将其提供给我的列表,让列表明白这意味着要分别获取切片,并在最后将它们的结果连接起来。


2
“Python不幸的是没有片段原语” 甚至没有 slice - Ignacio Vazquez-Abrams
抱歉,我应该说的是切片字面量,而不是切片原语。也就是说,你不能仅仅传递语法(0:10:2),好像它本身就代表了索引。你必须经过繁琐的额外层次,制作自己的切片对象,这会破坏切片语法的所有美好之处。请参见我的问题,也链接在上面 - ely
6个回答

10
一个切片制造器对象(例如您其他问题中的SliceMakernp.s_)可以接受多个逗号分隔的切片;它们被接收为slice或其他对象的tuple
from numpy import s_
s_[0, 3:5, 6::3]
Out[1]: (0, slice(3, 5, None), slice(6, None, 3))

NumPy使用这种方法来处理多维数组,但您也可以将其用于切片拼接:
def xslice(arr, slices):
    if isinstance(slices, tuple):
        return sum((arr[s] if isinstance(s, slice) else [arr[s]] for s in slices), [])
    elif isinstance(slices, slice):
        return arr[slices]
    else:
        return [arr[slices]]
xslice(list(range(10)), s_[0, 3:5, 6::3])
Out[1]: [0, 3, 4, 6, 9]
xslice(list(range(10)), s_[1])
Out[2]: [1]
xslice(list(range(10)), s_[:])
Out[3]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

它不能用于 xslice(list(range(10)), [1,2,3]),我的最新修订版也不行。只有我的第一个修订版可以。 - Dzhuang
@Dzhuang 谢谢!我改进了你的代码测试 insinstance(..., slice),把后面两个情况交换了一下。 - ecatmur
如果不是一个单独的索引0,而是一个索引列表,比如[0, 2],会怎么样呢? - undefined

2
import numpy as np
a = list(range(15, 50, 3))

# %%timeit -n 10000 -> 41.1 µs ± 1.71 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
[a[index] for index in np.r_[1:3, 5:7, 9:11]]
---
[18, 21, 30, 33, 42, 45]

import numpy as np
a = np.arange(15, 50, 3).astype(np.int32)

# %%timeit -n 10000 -> 31.9 µs ± 5.68 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
a[np.r_[1:3, 5:7, 9:11]]
---
array([18, 21, 30, 33, 42, 45], dtype=int32)

import numpy as np
a = np.arange(15, 50, 3).astype(np.int32)

# %%timeit -n 10000 -> 7.17 µs ± 1.17 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
slices = np.s_[1:3, 5:7, 9:11]
np.concatenate([a[_slice] for _slice in slices])
---
array([18, 21, 30, 33, 42, 45], dtype=int32)

看起来使用numpy是更快的方法。

ecatmur的答案中添加numpy部分。

import numpy as np
def xslice(x, slices):
    """Extract slices from array-like
    Args:
        x: array-like
        slices: slice or tuple of slice objects
    """
    if isinstance(slices, tuple):
        if isinstance(x, np.ndarray):
            return np.concatenate([x[_slice] for _slice in slices])
        else:
            return sum((x[s] if isinstance(s, slice) else [x[s]] for s in slices), [])        
    elif isinstance(slices, slice):
        return x[slices]
    else:
        return [x[slices]]

1

你最好自己编写序列类型。

>>> L = range(20)
>>> L
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> operator.itemgetter(*(range(1, 5) + range(10, 18, 3)))(L)
(1, 2, 3, 4, 10, 13, 16)

为了让您开始进行这方面的工作:

>>> operator.itemgetter(*(range(*slice(1, 5).indices(len(L))) + range(*slice(10, 18, 3).indices(len(L)))))(L)
(1, 2, 3, 4, 10, 13, 16)

我会使用 itertools.isliceitertools.chain 来构建它们。 - PaulMcG

1

我不确定这是否更好,但它有效,为什么不使用...

[y for x in [old_list[slice(*a)] for a in ((0,1),(3,201),(201,None,3))] for y in x]

它可能很慢(特别是与chain相比),但它是基本的Python(用于测试的版本为3.5.2)


0

为什么不为您的目的创建一个自定义切片呢?

>>> from itertools import chain, islice
>>> it = range(50)
>>> def cslice(iterable, *selectors):
    return chain(*(islice(iterable,*s) for s in selectors))

>>> list(cslice(it,(1,5),(10,15),(25,None,3)))
[1, 2, 3, 4, 10, 11, 12, 13, 14, 25, 28, 31, 34, 37, 40, 43, 46, 49]

1
你看过我上面提供的链接吗?那个问题的答案基本上体现了和你答案一样的东西,但是它语法更好且不需要 itertools。我的主要目标是直接将切片语法传递给函数。使用元组表示切片并将它们传递到新对象中的语法开销甚至比只是连接单独的切片还要糟糕。 - ely
@EMS:不,那个答案只是描述了一种轻松创建切片的方法,但它们不能直接用于索引列表中的元素。 Abhijit的解决方案可以实现你想要的功能,并且你可以将其与其他答案结合起来,以获得一个漂亮的语法,例如:cslice(it, make_slice[1:5], make_slice[10:15], make_slice[25::3]) - Bakuriu

0
你可以扩展list以允许多个切片和索引:
class MultindexList(list):
    def __getitem__(self, key):
        if type(key) is tuple or type(key) is list:
            r = []
            for index in key:
                item = super().__getitem__(index)
                if type(index) is slice:
                    r += item
                else:
                    r.append(item)
            return r
        else:
            return super().__getitem__(key)


a = MultindexList(range(10))
print(a[1:3])             # [1, 2]
print(a[[1, 2]])          # [1, 2]
print(a[1, 1:3, 4:6])     # [1, 1, 2, 4, 5]

这很聪明,但主要用例之一将是将“带有间隙的切片”传递给多维量的每个组件,因此以这种方式重载getitem,使逗号分隔的值全部应用于仅第一维将无济于事。我认为真正的问题在于像::23:8这样的切片语法不表示文字对象,并且没有内置支持对这些文字的区间、范围或集合算术。 - ely
确实。此外,我不确定是否存在任何标准语义来说明当元组或其他序列作为索引传递时的含义。例如,numpy 主要用于多维索引,而这里我们将其用于连接索引,可能会令人困惑。 - Stuart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接