【问题标题】:Efficient multiple, arbitrary index access in Python tuple?Python 元组中高效的多个任意索引访问?
【发布时间】:2011-11-07 02:29:27
【问题描述】:

我有一个很长的 Python 元组 t。我想尽可能有效地从t 中获取索引i1i2、...、iN 处的元素。最好的方法是什么?

一种方法是:

(1)    result = [t[j] for j in (i1, i2, ..., iN)]

但这似乎会导致对元组进行 N 次单独查找。有更快的方法吗?当 Python 进行这样的切片时:

(2)    result = t[1:M:3]

我假设它不执行 M/3 单独查找。 (也许它使用位掩码并执行单个复制操作?)我有什么方法可以利用 Python 在 (2) 中所做的任何事情来使我的任意索引切片发生在单个副本中?

谢谢。

【问题讨论】:

  • 索引内置序列类型的实例是您可以做的最快的事情之一。对它们进行切片比在循环中自己进行切片更有效的唯一原因是因为它是用 C 编写的,并且循环以及(甚至隐式)调用方法在 Python 中具有更大的开销。此外,只有在i1iN 是相同数字的倍数加上某个常数时,适用于切片的技巧(如果有这样的技巧......你必须以任何一种方式复制每个项目)才是可能的。跨度>
  • 你如何确定(i1...iN)?也许在效率(和简单性)方面可以获得收益,但只有在更广泛的范围内重写......
  • 这对我来说是一个有趣且令人惊讶的优化。您能否发布代码、性能测试和 cProfile 结果的链接供我们查看?

标签: python tuples slice


【解决方案1】:

如果您要进行大量相同的查找,则可能值得使用 itemgetter

from operator import itemgetter
mygetter = itemgetter(i1, i2, ..., iN)
for tup in lots_of_tuples:
    result = mygetter(tup)

首先,创建 itemgetter 的开销是不值得的

iPython 中的快速测试显示:

In [1]: import random

In [2]: from operator import itemgetter

In [3]: t=tuple(range(1000))

In [4]: idxs = tuple(random.randrange(1000) for i in range(20))

In [5]: timeit [t[i] for i in idxs]
100000 loops, best of 3: 2.09 us per loop

In [6]: mygetter = itemgetter(*idxs)

In [7]: timeit mygetter(t)
1000000 loops, best of 3: 596 ns per loop

显然,差异将取决于元组的长度、索引的数量等。

【讨论】:

  • 感谢您的提示和性能示例。我不知道 itemgetter,它准确地回答了我的问题。
【解决方案2】:

您列出的方法是从元组中获取元素的最佳方式。您通常不关心此类表达式的性能 - 这是一个过早的优化,即使您这样做了,即使进行了优化,此类操作也已经太慢了,即如果您优化访问,循环本身仍然会很慢,因为临时变量等的引用计数。

如果您已经遇到性能问题,或者这已经是 CPU 密集型代码的一部分,您可以尝试几种替代方法:

1) numpy 数组:

>>> arr = np.array(xrange(2000))
>>> mask = np.array([True]*2000)
>>> mask = np.array([False]*2000)
>>> mask[3] = True
>>> mask[300] = True
>>> arr[mask]
array([  3, 300])

2) 您可以使用 C API 来复制元素,使用 PyTuple_GET_ITEM 直接访问内部数组,但请注意,使用 C API 并非易事,并且会引入很多错误。

3) 您可以将 C 数组与 C API 一起使用,例如使用array.array 的缓冲区接口将数据访问粘合到 Python。

4) 您可以将 Cython 与 C 数组和自定义 Cython 类型一起用于从 Python 访问数据。

5) 您可以同时使用 Cython 和 numpy

【讨论】:

    【解决方案3】:

    在列表推导中有一个隐含的for 循环,我很确定它以合理的效率迭代元组值。我认为您无法提高对列表理解的效率。

    如果您只需要这些值,您或许可以使用生成器表达式并避免构建列表,从而稍微节省时间或内存。

    【讨论】:

      【解决方案4】:

      切片可以更有效,因为它有更多的约束:索引必须以线性方式进行固定数量。列表推导可能是完全随机的,因此无法进行优化。

      对效率做出假设仍然很危险。尝试两种方式计时,看看是否有显着差异。

      【讨论】:

        【解决方案5】:

        1) 您确定需要加快操作速度吗?

        2) 另一个选项是operator.itemgetter:它返回一个由其索引选择的元组:

        >>> t = tuple(string.ascii_uppercase)
        >>> operator.itemgetter(13,19,4,21,1)(t)
        ('N', 'T', 'E', 'V', 'B')
        

        operator 模块是用 C 语言实现的,因此可能会胜过 Python 循环。

        【讨论】:

          猜你喜欢
          • 2018-10-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多