从列表或元组中显式选择项目。

161
我有一个Python列表(也可以是元组):
myList = ['foo', 'bar', 'baz', 'quux']

我可以说

>>> myList[0:3]
['foo', 'bar', 'baz']
>>> myList[::2]
['foo', 'baz']
>>> myList[1::2]
['bar', 'quux']

如何明确选择没有特定模式的索引项?例如,我想选择[0,2,3]。或者从一个非常大的1000个项目的列表中,我想选择[87, 342, 217, 998, 500]。是否有一些 Python 语法可以实现?类似于以下代码:

>>> myBigList[87, 342, 217, 998, 500]

1
这似乎是一个重复的问题。另一个问题有更多的赞,但这个问题似乎有一个更好的答案和时间。 - AnnanFay
这个回答解决了你的问题吗?知道索引访问列表的多个元素 - malat
9个回答

207
list( myBigList[i] for i in [87, 342, 217, 998, 500] )

我用Python 2.5.2与以下答案进行了比较:

  • 19.7微秒: [ myBigList[i] for i in [87, 342, 217, 998, 500] ]

  • 20.6微秒: map(myBigList.__getitem__, (87, 342, 217, 998, 500))

  • 22.7微秒: itemgetter(87, 342, 217, 998, 500)(myBigList)

  • 24.6微秒: list( myBigList[i] for i in [87, 342, 217, 998, 500] )

请注意,在Python 3中,第1个答案已更改为与第4个相同。


另一个选择是从numpy.array开始,它允许通过列表或numpy.array进行索引:

>>> import numpy
>>> myBigList = numpy.array(range(1000))
>>> myBigList[(87, 342, 217, 998, 500)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: invalid index
>>> myBigList[[87, 342, 217, 998, 500]]
array([ 87, 342, 217, 998, 500])
>>> myBigList[numpy.array([87, 342, 217, 998, 500])]
array([ 87, 342, 217, 998, 500])

tuple 不同于切片(slice),它的工作方式不同。


3
最好使用列表推导式来实现, [myBigList[i] for i in [87, 342, 217, 998, 500]],但我最喜欢这种方法。 - Zach Kelling
@MedhatHelmy 这已经在答案中了。第三个选项在 python -mtimeit 的初始化部分中使用了 from operator import itemgetter - Dan D.
1
我想从语言设计的角度来看,为什么当myBigList是一个普通的Python list时,myBigList[(87, 342, 217, 998, 500)]不能正常工作? 当我尝试这样做时,会出现TypeError: list indices must be integers or slices, not tuple。 这比输入推导式要容易得多 - 是否涉及语言设计/实现问题? - sparc_spread
@sparc_spread,这是因为Python中的lists只接受整数或切片。传递一个整数可以确保从现有列表中仅检索一个项目。传递一个切片可以确保检索其中的一部分,但传递一个元组就像将一个数据类型(tuple)作为参数传递给另一个数据类型(list),这在语法上是不正确的。 - amanb
为什么要使用 list( myBigList[i] for i in [87, 342, 217, 998, 500] ) 而不是 [ myBigList[i] for i in [87, 342, 217, 998, 500] ] - Qbik
1
@Qbik 因为在Python 2中,生成器表达式会创建一个新的作用域,所以它不会泄漏循环变量。在Python 3中,列表推导已经被修改,不需要这样做了。虽然如此,我还是更喜欢这种方式,因为它使得将列表替换为任何其他生成器消耗函数更加容易。这也是我不喜欢字典推导和集合推导的原因。 - Dan D.

60

这个怎么样:

from operator import itemgetter
itemgetter(0,2,3)(myList)
('foo', 'baz', 'quux')

2
到目前为止,这是最性感的。喜欢那个 operator 模块! - jathanism

18
也许需要使用列表推导式:
L = ['a', 'b', 'c', 'd', 'e', 'f']
print [ L[index] for index in [1,3,5] ]

输出:

['b', 'd', 'f']

这是你要寻找的内容吗?


11

虽然Python没有内置的方法,但是你可以创建一个list的子类,将元组作为“索引”:

class MyList(list):

    def __getitem__(self, index):
        if isinstance(index, tuple):
            return [self[i] for i in index]
        return super(MyList, self).__getitem__(index)


seq = MyList("foo bar baaz quux mumble".split())
print seq[0]
print seq[2,4]
print seq[1::2]

打印

foo
['baaz', 'mumble']
['bar', 'quux']

2
不错的解决方案!有了这个扩展,Python中处理数组的方式开始看起来更像R或Matlab了。 - Assad Ebrahim

7
>>> map(myList.__getitem__, (2,2,1,3))
('baz', 'baz', 'bar', 'quux')

您可以创建自己的List类,支持元组作为参数传递给__getitem__,这样您就可以执行myList[(2,2,1,3)]

1
虽然这样做可以工作,但直接调用魔术变量通常不是一个好主意。最好使用列表推导或像 operator 这样的辅助模块。 - jathanism
@jathanism:我必须尊重地不同意。虽然如果你关心向前兼容性(而不是公共/私有),我绝对可以理解你的想法。 - ninjagecko
1
这就是我的出发点。 :) 接下来,使用len(myList)而不是myList.__len__()的原因也是相同的。 - jathanism
一个有创意的解决方案。我认为调用魔术变量并不是一个坏主意。程序员可以根据编程情况选择他们喜欢的方式。 - Jacob CUI
使用魔术方法通常是不好的,所以最好避免使用。除了可能出于性能原因外,它从来不是必要的。我不知道__getitem __()是否有任何特定之处,但对于其他示例,请参见为什么调用Python的“魔法方法”不像对应的运算符那样进行类型转换?是否存在任何情况,其中len(someObj)不调用someObj的__len__函数? - wjandrea
这些魔术方法的反例都是关于魔术方法如何实现一个“构建块”,可以作为更复杂协议的一部分使用。我认为没有理由不使用它,因为我们不期望更复杂的协议(或在这种情况下,该方法基本上就是协议)。当然,如果有证据证明我错了,我也很乐意接受。使用它可能会使您的代码在无效索引等情况下抛出IndexError(但是您的代码也可能使用负索引,这可能是积极的)-- https://docs.python.org/3/reference/datamodel.html#object.__getitem__ - ninjagecko

4

我想指出,即使itemgetter的语法看起来很整洁,但在大型列表上执行时速度会变慢。

import timeit
from operator import itemgetter
start=timeit.default_timer()
for i in range(1000000):
    itemgetter(0,2,3)(myList)
print ("Itemgetter took ", (timeit.default_timer()-start))

Itemgetter花费了1.065209062149279秒

start=timeit.default_timer()
for i in range(1000000):
    myList[0],myList[2],myList[3]
print ("Multiple slice took ", (timeit.default_timer()-start))

多重切片花费了0.6225321444745759秒


请在第一个代码片段中添加 myList = np.array(range(1000000)),否则会出现错误。 - Cloud Cho

2
另一个可能的解决方案:
sek=[]
L=[1,2,3,4,5,6,7,8,9,0]
for i in [2, 4, 7, 0, 3]:
   a=[L[i]]
   sek=sek+a
print (sek)

1

这是一个一行的lambda表达式:

list(map(lambda x: mylist[x],indices))

其中:

mylist=['a','b','c','d','e','f','g','h','i','j']
indices = [3, 5, 0, 2, 6]

输出:

['d', 'f', 'a', 'c', 'g']

1

通常当你有一个布尔类型的numpy数组,比如mask

[mylist[i] for i in np.arange(len(mask), dtype=int)[mask]]

适用于任何序列或np.array的lambda表达式:

subseq = lambda myseq, mask : [myseq[i] for i in np.arange(len(mask), dtype=int)[mask]]

newseq = subseq(myseq, mask)


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