我有一个大致如下结构的Python函数,它计算一些结果并将它们写入文件:
results = []
with open(filename, "w") as output:
for item in items:
result = compute_stuff(item)
write_result(item, result, output)
results.append(result)
return results
现在我并不总是想将结果写入文件 - 有时我只想计算它们并让它们返回。使“write_result”成为条件很容易,但是还有一种方法可以使“with”语句中的文件创建与条件相关吗? (我知道我可以明确地处理文件的打开和关闭,但是我会产生“try / finally”开销,创建“with”语句以避免。)
对此有优雅的解决方案吗?
已编辑添加:
我可能过于简化了这个例子。我没有写任意文件,而是使用matplotlib.backends.backend_pdf.PdfPages
(link),并在每一步中添加一个图(PDF中的新页面)。特别是,这意味着我无法使用PdfPages
重新打开PDF文件,因为它会被覆盖。
答案 0 :(得分:9)
您可以编写自己的上下文管理器功能:
class Dummysink(object):
def write(self, data):
pass # ignore the data
def __enter__(self): return self
def __exit__(*x): pass
def datasink(filename):
if filename:
return open(filename, "w")
else:
return Dummysink()
...
results = []
with datasink(filename) as output:
for item in items:
result = compute_stuff(item)
write_result(item, result, output)
results.append(result)
return results
答案 1 :(得分:2)
听起来您需要将一个函数传递到范围中,该范围封装了您可能想要或可能不想将结果存储到文件中的事实。在OO语言中,这将被称为策略模式,但在Python中,您可以传入一个函数(因为函数是第一类的。)
my_gen = (compute_stuff(item) for item in items)
results = store_strategy(my_gen)
return results
其中store_strategy
可能只是已经包含with
语句的内容。
def pass_through(iterable):
return iterable
def file_store(filename):
def store(items):
with open(filename, 'w') as output:
results = []
for item in items:
write_result(item, result, output)
result.append(item)
return results
return store
答案 2 :(得分:2)
使用辅助函数来包装真实的open()
,它可以调用真实的open()
,也可以返回一个具有方法write()
,flush()
和{{1}的对象}}:
close()
答案 3 :(得分:2)
大多数(如果不是全部)其他答案描述了如何编写允许您按照自己的意愿执行操作的上下文管理器。这里有一些不同的东西直接解决了你的问题:
是否可以有条件地使用“with”语句?
是的,它可以 - 将generator function与for
循环一起使用,有条件地不迭代。这是一个可运行的示例,基于您问题中的代码,显示我的意思:
# Scaffolding added for testing.
def compute_stuff(item):
return 'item: ' + str(item)
def write_result(item, result, output):
output.write(result + '\n')
# Generator function.
def conditionally_with(filename, mode='r'):
if not filename: # Check condition.
return
else:
with open(filename, mode) as opened:
yield opened
if __name__ == '__main__':
filename = 'foobar.txt'
items = range(5)
results = []
for output in conditionally_with(filename, "w"):
for item in items:
result = compute_stuff(item)
write_result(item, result, output)
results.append(result)
print(results) # -> ['item: 0', 'item: 1', 'item: 2', 'item: 3', 'item: 4']
# return results
答案 4 :(得分:1)
使用协同程序
http://www.dabeaz.com/coroutines/Coroutines.pdf(由Paulo Scardine建议)
如果我们想写:
def writer(filename):
with open(filename, "w") as output:
while True:
try:
item, result = (yield)
write_result(item, result, output)
except GeneratorExit:
output.flush()
break
如果我们不这样做:
def dummy_writer():
while True:
yield
初始化我们的协程:
result_writer = writer(filename) if filename else dummy_writer()
result_writer.next()
运行我们的代码:
results = []
for item in items:
result = compute_stuff(item)
result_writer.send((item, result))
results.append(result)
result_writer.close()
return results
答案 5 :(得分:1)
这是从wheaties' answer中的建议中得出的一些东西,我认为这可能是最好的无上下文管理器的方法(因此应该使用示例代码来更具体地说明它):
def create_list():
return list
def file_store(filename, mode='w'):
def store(items):
with open(filename, mode) as output:
results = []
for item in items:
write_result(item, output)
results.append(item)
return results
return store
store_strategy = file_store(filename) if filename else create_list()
results = store_strategy(compute_stuff(item) for item in items)
return results
答案 6 :(得分:0)
您要做的是导致文件的后期创建。您想要的是看起来像上下文管理器的东西,但在您需要它之前实际上并不创建该文件。您需要自己实施__enter__
和__exit__
。这是一个(非常)缩写示例,与完整示例相比,仅针对您的具体情况:
class LateFile(object):
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.fp = None
def __enter__(self):
# Do nothing - we don't open the file yet
return(self)
def __exit__(self, exctype, value, traceback):
if self.fp != None:
fp.close()
def write(self, *args, **kwargs):
if self.fp == None:
self.fp = open(self.filename, self.mode)
self.fp.write(*args, **kwargs)
这些方面的东西。
然后,要使用它,请执行以下操作:
with LateFile(filename, "w") as output:
for item in items:
result = compute_stuff(item)
if should_write_result(item, result):
write_result(item, result, output)
results.append(result)
write_result
应将output
视为普通文件对象;您需要将方法反映或委托给它。这样做,如果没有写入结果,则不会创建文件,但如果写入一个结果,则将创建该文件。写给。