【问题标题】:How to avoid creating unnecessary lists?如何避免创建不必要的列表?
【发布时间】:2018-05-23 03:21:09
【问题描述】:

我经常遇到这样的情况:我从文件或任何地方提取一些信息,然后必须通过几个步骤将数据按摩成最终所需的形式。例如:

def insight_pull(file):
    with open(file) as in_f:
        lines = in_f.readlines()

        dirty = [line.split('    ') for line in lines]
        clean = [i[1] for i in dirty]
        cleaner = [[clean[i],clean[i + 1]] for i in range(0, len(clean),2)]
        cleanest = [i[0].split() + i[1].split() for i in cleaner]


        with open("Output_File.txt", "w") as out_f:
            out_f.writelines(' '.join(i) + '\n' for i in cleanest)

如上例:

    # Pull raw data from file splitting on '   '.
    dirty = [line.split('    ') for line in lines]

    # Select every 2nd element from each nested list.
    clean = [i[1] for i in dirty]

    # Couple every 2nd element with it's predecessor into a new list.
    cleaner = [[clean[i],clean[i + 1]] for i in range(0, len(clean),2)]

    # Split each entry in cleaner into the final formatted list.
    cleanest = [i[0].split() + i[1].split() for i in cleaner]

鉴于我不能将所有编辑放入一行或循环中(因为每个编辑都取决于之前的编辑),有没有更好的方法来构建这样的代码?

如果问题有点含糊,我们深表歉意。非常感谢任何意见。

【问题讨论】:

  • 从技术上讲,你说你不能在一行中完成这一切是错误的,因为你可以对其他列表推导的输出进行列表推导,但从道德上讲,你不应该这样做。
  • 你能展示一些示例输入和输出吗?
  • 不幸的是,没有一些数据是敏感的,但上面的例子只是为了说明这一点。这种问题不断出现在我面前。
  • 只是组成一些随机输入并显示预期的输出。可能有更好的方法来做您正在尝试的事情,但除非我们看看一些有代表性的例子,否则我们无能为力。

标签: python file list-comprehension string-iteration value-iteration


【解决方案1】:

生成器表达式

您不想创建多个列表是正确的。您的列表理解创建了一个全新的列表,浪费了内存,并且您正在遍历每个列表!

@VPfB 使用生成器的想法是一个很好的解决方案,如果您的代码中有其他地方可以重用生成器。如果您不需要重用生成器,请使用生成器表达式。

生成器表达式是惰性的,就像生成器一样,所以当链接在一起时,就像这里一样,循环将在结束时计算一次,当调用 writelines 时。

def insight_pull(file):
    with open(file) as in_f:
        dirty = (line.split('    ') for line in in_f)    # Combine with next
        clean = (i[1] for i in dirty)
        cleaner = (pair for pair in zip(clean,clean))    # Redundantly silly
        cleanest = (i[0].split() + i[1].split() for i in cleaner)

        # Don't build a single (possibily huge) string with join
        with open("Output_File.txt", "w") as out_f:
            out_f.writelines(' '.join(i) + '\n' for i in cleanest)

保留上面的内容,因为它直接匹配您的问题,您可以更进一步:

def insight_pull(file):
    with open(file) as in_f:
        clean = (line.split('    ')[0] for line in in_f)
        cleaner = zip(clean,clean)
        cleanest = (i[0].split() + i[1].split() for i in cleaner)

        with open("Output_File.txt", "w") as out_f:
            for line in cleanest:
                out_f.write(line + '\n')

【讨论】:

  • 很多很棒的答案,但我认为你一针见血。我不得不复习我对生成器表达式的理解。谢谢!
【解决方案2】:

我从您的示例中假设只有cleanest 列表对您有任何实际价值,其余的只是中间步骤,可以毫无顾虑地丢弃。

假设是这样,为什么不在每个中间步骤重用相同的变量,这样你就不会在内存中保存多个列表?

def insight_pull(file):
    with open(file) as in_f:
        my_list = in_f.readlines()

        my_list = [line.split('    ') for line in my_list]
        my_list = [i[1] for i in my_list]
        my_list = [[my_list[i],my_list[i + 1]] for i in range(0, len(my_list),2)]
        my_list = [i[0].split() + i[1].split() for i in my_list]


    with open("Output_File.txt", "w") as out_f:
        out_f.writelines(' '.join(i) + '\n' for i in my_list)

【讨论】:

  • 这将节省 x5 内存,但每个列表解析都在分配和创建一个全新的列表。
【解决方案3】:

如果您正在考虑性能,那么您正在寻找生成器。生成器很像列表,但它们的评估是惰性的,这意味着每个元素只在需要时才生成。例如,在下面的序列中,我实际上并没有创建 3 个完整列表,每个元素只评估一次。以下只是生成器的一个示例使用(据我了解,您的代码只是您遇到的问题的一个示例,而不是具体问题):

# All even values from 2-18
even = (i*2 for i in range(1, 10))

# Only those divisible by 3
multiples_of_3 = (val for val in even if val % 3 == 0)

# And finally, we want to evaluate the remaining values as hex
hexes = [hex(val) for val in multiples_of_3]
# output: ['0x6', '0xc', '0x12']

前两个表达式是生成器,最后一个只是列表推导式。当有很多步骤时,这将节省大量内存,因为您不会创建中间列表。请注意,生成器不能被索引,它们只能被评估一次(它们只是值流)。

【讨论】:

    【解决方案4】:

    为了实现这个目标,我会推荐流水线处理。我找到了一篇解释该技术的文章:generator pipelines

    这是我将循环直接转换为管道的尝试。该代码未经测试(因为我们没有要测试的数据)并且可能包含错误。

    func 名称中前导的f 代表过滤器。

    def fromfile(name):
        # see coments
        with open(name) as in_f:
            for line in in_f:
                yield line
    
    def fsplit(pp):
        for line in pp: 
            yield line.split('    ')
    
    def fitem1(pp):
        for item in pp: 
            yield item[1]
    
    def fpairs(pp):
        # edited
        for x in pp:
            try:
                yield [x, next(pp)]
            except StopIteration:
                break
    
    def fcleanup(pp):
        for i in pp: 
            yield i[0].split() + i[1].split()
    
    pipeline = fcleanup(fpairs(fitem1(fsplit(fromfile(NAME)))))
    
    output = list(pipeline)
    

    对于实际使用,我会聚合前 3 个过滤器以及接下来的 2 个过滤器。

    【讨论】:

    • 好主意,如果这些函数将被重用......我不会 1) 为文件创建一个生成器,它已经是一个。 2) fpairs() 可能引发未处理的 StopIteration。
    • @digitalsentinel 关于文件已经是生成器的好点。我决定保留代码原样,仅用于说明。关于 StopIteration,它将表示fpairs 已用尽。这是正确的,我认为。试试:print(list(fpairs(iter((1,2,3,4,5))))),它会打印出[[1, 2], [3, 4]]
    • @digitalsentinel 关于 StopIteration,我错了。现在 PEP479 认为它不正确。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-03
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多