Python 为什么我不想立即将所有的iterables转换为具有定义长度的内容(例如列表)?

Python 为什么我不想立即将所有的iterables转换为具有定义长度的内容(例如列表)?,python,Python,我不知道为什么我不总是转换成列表。为什么我不想立即将所有iterables转换为具有定义长度的内容(如列表)?欢迎加入社区 “将iterable转换为列表”通常是一种O(N)/线性操作 “处理列表中的所有元素”通常是一种O(N)/线性操作 在对iterable进行操作之前,通常不应将其转换为列表的原因是,对于只需执行N操作的对象,iterable将执行2*N操作 (正如一些用户在评论中有益地提到的那样,这里存在一些对大O复杂度表示法的严重滥用。对于时间复杂度界限,我们忽略常数,而将重点放在与极限

我不知道为什么我不总是转换成列表。为什么我不想立即将所有iterables转换为具有定义长度的内容(如列表)?

欢迎加入社区

  • “将iterable转换为列表”通常是一种
    O(N)
    /线性操作
  • “处理列表中的所有元素”通常是一种
    O(N)
    /线性操作
  • 在对iterable进行操作之前,通常不应将其转换为列表的原因是,对于只需执行
    N
    操作的对象,iterable将执行
    2*N
    操作

    (正如一些用户在评论中有益地提到的那样,这里存在一些对大O复杂度表示法的严重滥用。对于时间复杂度界限,我们忽略常数,而将重点放在与极限中类似算法相比的行为上。这里我们将重点放在一个特定的案例上,并对其中一些算法的性能进行基准测试在实践中。)


    以下是我们感兴趣的两个案例:

    def compute_over_iterable():
    “”“将'values'初始化为iterable,并对其进行计算。”“”
    值=范围(10000)
    结果=[]
    对于输入值:
    结果.追加(条目+1)
    返回结果
    
    def compute_over_iterable_to_list():
    “”“将`values`初始化为iterable,转换为列表,然后计算:”“”
    值=列表(范围(10000))
    结果=[]
    对于输入值:
    结果.追加(条目+1)
    返回结果
    
    通过
    pytest benchmark
    ,我们可以看到这些案例的执行情况:

    ----------------------------------- benchmark: 2 tests -----------------------------------
    Name (time in us)                Min                   Max                  Mean          
    ------------------------------------------------------------------------------------------
    test_iterable               781.3790 (1.0)      1,118.6880 (1.0)        837.1683 (1.0)    
    test_iterable_to_list     1,000.4090 (1.28)     1,524.6950 (1.36)     1,138.6464 (1.36)   
    ------------------------------------------------------------------------------------------
    
    在本例中,我们看到(2)将iterable转换为列表所需的时间比直接使用iterable要长约1.36倍

    由于
    list(范围(10000))
    cast是在Python的底层C实现中处理的,因此这两种方法的结果非常接近。如果完全在Python中执行此操作,则差异更为明显:

    def compute_over_iterable_list_comprehension():
    值=范围(10000)
    值\u cast=[i代表值中的i]
    结果=[]
    对于值中的输入\u cast:
    结果.追加(条目+1)
    返回结果
    
    我们看到,直接使用iterable需要约1.5倍的时间:

    ------------------------------------------ benchmark: 3 tests ------------------------------------------
    Name (time in us)                              Min                   Max                  Mean          
    --------------------------------------------------------------------------------------------------------
    test_iterable                             844.9320 (1.0)      1,014.3210 (1.0)        896.8944 (1.0)    
    test_iterable_to_list                   1,079.2420 (1.28)     1,370.0570 (1.35)     1,140.3908 (1.27)   
    test_iterable_to_list_comprehension     1,269.8290 (1.50)     1,662.9790 (1.64)     1,336.5758 (1.49)   
    --------------------------------------------------------------------------------------------------------
    

    一些例外和有趣的案例
    MemoryView
    “@user2357112支持Monica”,我不熟悉。它涉及:

    “这通常需要一个对象,该对象具有将其转换为列表的方法,而不仅仅是调用列表。它仍然会浪费内存,通常不会节省很多时间,因此通常不值得费心。”

    出于好奇,我将这些添加到了一个单独的基准中。下面是一个基本概述,重点介绍了三种情况:

    def compute_over_memory_view():
    值=内存视图(b'x'*10000)
    # ...
    def compute_over_memory_view_tolist():
    值=内存视图(b'x'*10000)
    对于values.tolist()中的条目:
    # ...
    def compute_over_memory_view_cast():
    值=内存视图(b'x'*10000)
    值\u cast=列表(值)
    # ...
    
    我的基准测试似乎表明,在memoryview对象上调用
    tolist
    方法与直接在memoryview上操作几乎没有区别,但相比之下,转换到列表(
    list(values)
    )要慢得多

    -------------------------- benchmark 'Memory Views': 3 tests ---------------------------
    Name (time in us)                Min                Mean                   Max          
    ----------------------------------------------------------------------------------------
    test_memory_view            711.5030 (1.0)      774.2104 (1.0)        990.5890 (1.0)    
    test_memory_view_tolist     745.4060 (1.05)     822.1782 (1.06)     1,154.2550 (1.17)   
    test_memory_view_cast       860.5850 (1.21)     995.6708 (1.29)     1,290.4440 (1.30)   
    ----------------------------------------------------------------------------------------
    
    无限长不可数 “@Tomerikoo”提出了另一个很好的观点:iterable可以是无限的。在这种情况下,试图将iterable转换为有限长度的东西会让你陷入无限循环

    下面是一个最简单的示例:

    来自itertools导入周期的
    >>
    >>>从时间上导入睡眠
    >>>对于循环中的i(范围(3)):
    …睡眠(0.5)
    …印刷品(一)
    ... 
    0
    1.
    2.
    0
    1.
    2.
    0
    
    我们已经讨论过,“将iterable转换为list”是一个需要遍历list的所有元素的操作。 因此,以下语句是一个无限循环:

    列表(循环(范围(3))
    当源数据太大而无法放入内存时会发生什么情况?特别是如果您可以以原子方式处理数据,则无需分配所有内存(并可能会破坏RAM)你为什么要这样做?通常,实际上大多数时候,我只需要迭代一个iterable。我不关心它的长度。假设我使用的是像
    映射
    对象这样的东西。为什么不必要地浪费内存来将它转换成一个列表?我想这是对的。谢谢你的帮助。另外一个想法:如果你的数据需要一段时间来计算,你可以如果你一次只需要一个元素,你不想花那么多时间计算列表中的所有元素。你可以处理每一个元素,同时计算更多元素。iterable没有大小的元素是什么?假设你在重复轮询某个元素,或者只是使用一个无限生成器(如
    itertools.count
    )。不可能将其放入列表中,正如其他人所说,为什么您甚至希望将其作为列表?大多数情况下,我们只需要对某些内容进行迭代。然后使用迭代器而不是列表,不,对不起,但这是不正确的。因此,假设您有类似于
    data=map的内容(some\u func,some\u iterable)
    。如果对数据中的x执行
    操作:执行某事(x)
    ,假设
    执行某事
    某些函数在固定时间内工作,整个算法是线性时间。如果首先转换为列表,它仍然是线性时间,例如
    数据=列表(数据);对于数据中的x:执行某事(x)
    。它只会是O(N**2)如果你在每次迭代中都进行这种转换,这通常是没有意义的(尽管我在“野外”看到过更疯狂的事情),做两个O(N)的事情仍然是O(N)。如果你做的事情时间与N成正比,与N成正比,那么这就是O(N^2).谢谢你的关注,我已经用标签写了一个快速的基准来演示这个,但没有抓住m