【问题标题】:Generator comprehension with open function具有开放功能的生成器理解
【发布时间】:2022-01-20 10:48:45
【问题描述】:

我试图找出在逐行解析文件时使用生成器的最佳方法。 哪个使用生成器理解会更好。

第一个选项。

with open('some_file') as file:
    lines = (line for line in file)

第二个选项。

lines = (line for line in open('some_file'))

我知道它会产生相同的结果,但哪一个会更快/更高效?

【问题讨论】:

  • 第二个实例什么时候关闭文件?我想大多数人会期待第一种方式而不是第二种方式。
  • file 已经是一个可按需生成行的可迭代对象;没有理由创建这样的包装器生成器。
  • @chepner 有 - 当您想要传递它(即作为函数参数)并仍然让上下文管理器自动为您关闭它时。只有当你就地迭代文件时,才不需要包装它。
  • @Tomalak 是的,但这需要一个生成器 function 来创建一个封装上下文管理器的生成器。这里提到的两个选项都没有这样做。
  • @chepner 是的。我认为 OP 正是在寻找这条建议。

标签: python file generator


【解决方案1】:

您不能组合生成器和上下文管理器(with 语句)。

生成器很懒惰。他们不会真正读取他们的源数据,直到有东西向他们请求项目。

看来可以工作了:

with open('some_file') as file:
    lines = (line for line in file)

但是当你真正尝试稍后在程序中读取一行时

for line in lines:
    print(line)

ValueError: I/O operation on closed file. 会失败

这是因为上下文管理器已经关闭了文件——这是它生命中的唯一目的——并且生成器直到 for 循环开始实际请求数据时才开始读取它。

你的第二个建议

lines = (line for line in open('some_file'))

遇到相反的问题。你 open() 文件,但除非你手动 close() 它(你不能因为你不知道文件句柄),它将永远保持打开状态。这正是上下文管理器解决的问题。

总的来说,如果你想读取文件,你可以……读取文件:

with open('some_file') as file:
    lines = list(file)

或者你可以使用真正的生成器:

def lazy_reader(*args, **kwargs):
    with open(*args, **kwargs) as file:
        yield from file

然后你就可以了

for line in lazy_reader('some_file', encoding="utf8"):
    print(line)

lazy_reader() 将在读取最后一行时关闭文件。

【讨论】:

  • 这就是我需要的解释。谢谢。
【解决方案2】:

如果您想测试这样的东西,我建议您查看timeit 模块。

让我们为您的两个测试设置一个工作版本,我将添加一些性能相同的附加选项。

这里有几个选项:

def test1(file_path):
    with open(file_path, "r", encoding="utf-8") as file_in:
        return [line for line in file_in]

def test2(file_path):
    return [line for line in open(file_path, "r", encoding="utf-8")]

def test3(file_path):
    with open(file_path, "r", encoding="utf-8") as file_in:
        return file_in.readlines()

def test4(file_path):
    with open(file_path, "r", encoding="utf-8") as file_in:
        return list(file_in)

def test5(file_path):
    with open(file_path, "r", encoding="utf-8") as file_in:
        yield from file_in

让我们用一个文本文件来测试它们,该文件是我在进行此类测试时碰巧拥有的莎士比亚全集的 10 倍。

如果我这样做:

print(test1('shakespeare2.txt') == test2('shakespeare2.txt'))
print(test1('shakespeare2.txt') == test3('shakespeare2.txt'))
print(test1('shakespeare2.txt') == test4('shakespeare2.txt'))
print(test1('shakespeare2.txt') == list(test5('shakespeare2.txt')))

我看到所有测试都产生相同的结果。

现在让我们计时:

import timeit

setup = '''
file_path = "shakespeare2.txt"

def test1(file_path):
    with open(file_path, "r", encoding="utf-8") as file_in:
        return [line for line in file_in]

def test2(file_path):
    return [line for line in open(file_path, "r", encoding="utf-8")]

def test3(file_path):
    with open(file_path, "r", encoding="utf-8") as file_in:
        return file_in.readlines()

def test4(file_path):
    with open(file_path, "r", encoding="utf-8") as file_in:
        return list(file_in)

def test5(file_path):
    with open(file_path, "r", encoding="utf-8") as file_in:
        yield from file_in
'''

print(timeit.timeit("test1(file_path)", setup=setup, number=100))
print(timeit.timeit("test2(file_path)", setup=setup, number=100))
print(timeit.timeit("test3(file_path)", setup=setup, number=100))
print(timeit.timeit("test4(file_path)", setup=setup, number=100))
print(timeit.timeit("list(test5(file_path))", setup=setup, number=100))

在我的笔记本电脑上显示:

9.65
9.79
9.29
9.08
9.85

向我建议从性能角度选择哪一个并不重要。所以不要使用你的test2() 策略:-)

请注意,尽管从内存管理的角度来看,test5()(归功于 @tomalak)可能很重要!

【讨论】:

    猜你喜欢
    • 2016-10-13
    • 1970-01-01
    • 2011-01-31
    • 2012-09-20
    • 1970-01-01
    • 2017-12-02
    • 1970-01-01
    • 2023-03-14
    • 2014-12-23
    相关资源
    最近更新 更多