我必须打开一些文本文件并按行读取,并仅返回包含数字的字符串。
在with
中使用_iter__
语句是一个好主意吗?喜欢:
def __iter__(self):
with open(file_name) as fp:
for i in fp:
if is_number(i):
yield i
更好的方法是:
def __enter__(self):
self._fp = open(self._file, 'r')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._fp.close()
def __iter__(self) -> int:
for tracker_id in self._fp:
if re.search('\d', tracker_id):
yield int(tracker_id)
答案 0 :(得分:2)
您需要一个生成器,而不是上下文管理器。要创建一个,您可以尝试这样的事情:
import re
def filter_lines(filename: str, pattern: str):
p = re.compile(pattern)
with open(filename) as f:
for line in f:
if re.search(p, line):
yield line
if __name__ == "__main__":
for line in filter_lines('myfile.txt', '\d'):
print(line)
如果要多次使用它们,请记住要编译它们的正则表达式。
答案 1 :(得分:1)
我认为代码的第二种形式更好。
第一个版本依赖于__iter__
返回的迭代器,仅在进行迭代时存在。如果在不取消分配迭代器的情况下碰巧发生了迭代中断,则该文件可能会无限期保持打开状态。
像这样使用它通常是安全的,因为如果循环体中发生异常,则对象及其迭代器将被垃圾回收,因为除for
之外没有其他对迭代器的引用循环本身(尽管如果关闭了垃圾收集,则在CPython之外的其他解释器上可能并不安全):
for x in Whatever(): # assuming your methods are in a class named Whatever
# do stuff
这种替代用法可能并不安全,因为迭代器将存在于堆栈框架中,并且在处理异常时可能会存在很长一段时间:
it = iter(Whatever())
for x in it:
# do stuff
您的代码的第二种形式明确表明,调用代码负责确保正确清理资源。您可以使用类似这样的名称进行调用,并且可以确信如果引发异常,文件将被关闭:
with Whatever() as w:
for x in w:
# do stuff
第二个版本的代码的主要缺点是,您不能同时对同一个对象进行多次迭代,因为它们共享同一个文件对象。如果有人想在同一个文件上进行两次迭代,则需要创建该类的多个实例。
如果对象本身是迭代器,则它的一次性使用性质可能会更自然,而不仅仅是可迭代(例如,这就是文件对象的工作方式):
class Whatever:
def __enter__(self):
self._fp = open(self._file, 'r')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._fp.close()
def __iter__(self):
return self
def __next__(self)
tracker_id = next(self._fp)
while re.search('\d', tracker_id) is None:
tracker_id = next(self._fp)
return int(tracker_id)
请注意,我们故意不尝试捕获文件上调用StopIteration
可能引发的任何next
异常,因为这将表明我们也已完成。
答案 2 :(得分:0)
在第一种情况下,当请求迭代时将打开文件。如果完成多次迭代,则可能会导致额外的I / O。在第二种情况下,即使未进行任何迭代,在with
语句中使用对象时也始终打开文件。
需要权衡取舍-一种方法可能更有效,具体取决于对象的使用方式。如果您需要支持多种使用模式,则可能需要结合使用这些方法。在第一次请求迭代时延迟打开文件,然后在__exit__
中将其关闭。如果您不需要这种灵活性,请选择最适合该对象使用方式的选项。