![](/img/trans.png)
[英]Returning generator comprehension in function causing NoneType not iterable error
[英]Generator comprehension with open function
我試圖弄清楚在逐行解析文件時使用生成器的最佳方法是什么。 生成器理解的哪種使用會更好。
第一個選項。
with open('some_file') as file:
lines = (line for line in file)
第二種選擇。
lines = (line for line in open('some_file'))
我知道它會產生相同的結果,但哪一個會更快/更有效?
您不能將生成器和上下文管理器( 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()
將關閉文件。
如果你想測試這樣的東西,我建議查看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()
策略:-)
請注意,盡管從 memory 管理的角度來看, test5()
(歸功於@tomalak)可能很重要。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.