在python中创建包含整数的类似列表的对象的最快方法

Fastest way to make a list-like object containing integers in python

在python中,创建包含整数/浮点数(非常简单的数据类型)的类似列表的对象的最快方法是什么?"清单"是什么意思?

这意味着我希望有一个对象支持列表的两个(非常)基本操作:获取某个索引(1)中的对象并更改其值(2)。

在发布这个之前我遇到了什么帖子?为什么他们没有解决我的问题?

我遇到了这两个[1][2]

他们没有解决我的问题,因为他们的所有解决方案都太慢了:在我的电脑中,array.array('i',(0,)*10 ** 8)导致了一个错误(lol);[0 for _ in range(10**8)]花费了大约15秒(哇!);[0] * 10 ** 8用了2.3秒;[None] * 10 ** 8用了1.8秒;(1.8秒可能更快…)

我想做什么?

我试过使用ctypes模块

1
2
from ctypes import c_int
array = (c_int * 10 ** 8)()

上面的代码只花了0.7秒…但是有没有办法让它更快呢?除了速度快之外,它还有一些缺点:

  • 当它使用C/C++变量的骨架时,它的整数将处于"不等于Python"的整数值范围内。
  • 列表中不能有多个数据类型
  • 您必须导入一个模块才能使用它
  • 真的可以按我的要求去做吗?有没有比使用ctypes模块更快的方法?如果是这样,请确保您使用的是"内置"/"预安装"模块。

    编辑:为什么我不能简单地安装一些模块,比如numpy?

    我正在使用Python进行有竞争力的编程,大多数解释器/评委都不允许使用外部库。

    我们可以用array.array存储自定义对象吗?

    我可以看到许多答案使用了array模块的array功能。它们都使用"i"来指定我们要存储整数。是否可以创建一个类并创建一个包含该类的"array.array"?例如:

    1
    2
    3
    4
    5
    6
    7
    8
    class Point:
     def __init__(self, x, y):
      self.x = x
      self.y = y

    # make array.array object with all indexes containing a Point with atributes x and y with value 0
    # an example with a list of what I want to do is this:
    # l = [Point(0, 0) for _ in range(10**3)]


    array.array('i',(0,) * 10**8) resulted in an error (lol)

    您没有指定您得到了什么错误-这对我很有效,尽管它不是很快,因为它构建了一个中间元组并立即丢弃它。使用python的内置类型,如果避免使用tuple:

    1
    a = array.array('i', (0,)) * 10**8

    The code above took only 0.7 seconds ... but is there a way to make it faster?

    如果不允许创建或导入C扩展,那么很难击败array.array。在我的几年前的机器上,上述操作需要0.6秒。您可以通过增加初始数组的大小来进一步优化它。例如,这会产生相同的结果,但速度快了3倍!!):

    1
    2
    # 0.22 s
    a = array.array('i', (0,) * 10) * 10**7

    在我的计算机上,以下版本最有效:

    1
    2
    # 0.19 s
    a = array.array('i', (0,) * 100) * 10**6

    进一步增加初始阵列大小没有帮助,很快就会开始降低性能。

    为了获得更好的效率,请考虑其他方法,例如一个懒惰的列表或为您的用例定制的完全不同的数据结构。考虑到竞争的背景,这可能正是人们真正想要的。

    但是,请注意,每个解决方案都会有不同的权衡。例如,@konstantinnikitin提供的一个延迟数组构造起来非常有效,但是它的__getitem____setitem__在纯python中实现,比list或array.array慢几个数量级。对你来说,哪一个更好,归根结底就是什么操作在你的程序中更频繁,这取决于你自己去发现。


    我只使用支持快速阵列操作的numpy模块。

    例如,用数字0到10**8创建一个数组:

    1
    2
    3
    4
    5
    6
    7
    8
    import numpy as np
    import time

    b = time.time()
    a = np.linspace(0, 10**8, 10**8)
    c = time.time()
    print(c-b)
    >>>0.5000154972076416

    或者制作一个10*8长的0数组:

    1
    2
    3
    4
    5
    b = time.time()
    a = np.zeros(shape=(10**8,))
    c = time.time()
    print(c-b)
    >>>0.0

    numpy如此之快的主要原因是它是在C中实现的。

    编辑:如果只想使用预安装的软件包,可以尝试使用array软件包:

    1
    2
    3
    4
    5
    6
    import array
    import time
    r = time.time()
    a = array.array('i', [0]) * (10**8)
    print(time.time()-r)
    >>>0.15627217292785645


    如果您只想要这两个属性:

    getting an object in a certain index (1) and changing its value (2)

    那么你只需使用一个collections.defaultdict

    1
    2
    import collections
    my_list = collections.defaultdict(lambda: 0)

    相当快(~0.4μs):

    1
    2
    $ python3 -m timeit -s 'import collections' 'collections.defaultdict(lambda: 0)'
    1000000 loops, best of 3: 0.417 usec per loop

    然而,实际使用它可能会比其他答案中建议的任何类型慢很多。


    我想说你可以尝试不同的方法:

    1)numpy。它确实是阵列的标准。对于每个操作来说,跨越python<->c边界的成本都会增加,但这实际上取决于您的任务。

    1
    2
    3
    4
    x = numpy.array(10 ** 8)

    timeit.timeit('x = numpy.array(10 ** 8)', 'import numpy', number=1)
    4.195800283923745e-05

    2)延迟初始化(如javascript数组)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class LazyArray:
        def __init__(self, size):
            self.storage = {}
            self.size = size

        def check(self, i):
            if i < 0 or i >= self.size:
                raise RuntimeError()

        def __getitem__(self, i):
            self.check(i)
            return self.storage.get(i, 0)

        def __setitem__(self, i, value):
            self.check(i)
            self.storage[i] = value

    x = LazyArray(10 ** 8)
    x[10]
    >> 0
    x[10] = 5
    x[10]
    >> 0


    对于只需要0到255之间的整数的情况,bytearray对象创建速度非常快:

    1
    2
    3
    4
    5
    6
    >>> timeit.timeit('bytearray(100000)', number=1000)
    0.005567271093696036
    >>> timeit.timeit('array.array("B", [0])*100000', 'import array', number=1000)
    0.36631167401839093
    >>> timeit.timeit('array.array("i", [0])*100000', 'import array', number=1000)
    0.56494557472422

    array.array不同,它直接将分配归零,而不是从用零初始化的对象复制。