13

我有一个大致如下结构的 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”语句中的文件创建依赖于条件吗?(我知道我可以明确地处理文件的打开和关闭,但我会产生创建“with”语句以避免的“try/finally”开销。)

有没有一个优雅的解决方案?

编辑添加: 我可能过于简化了这个例子。我没有写入任意文件,而是使用matplotlib.backends.backend_pdf.PdfPages链接),并在每一步中添加一个图(PDF 中的新页面)。特别是,这意味着我无法使用 重新打开 PDF 文件PdfPages,因为它会被覆盖。

4

7 回答 7

10

您可以编写自己的上下文管理器函数:

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
于 2014-03-06T14:05:22.623 回答
2

大多数(如果不是全部)其他答案都描述了如何编写一个上下文管理器,它可以让你做你想做的事。这里有一些不同的东西可以直接解决您的问题:

可以有条件地使用“with”语句吗?

是的,它可以——通过将生成器函数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
于 2014-03-06T16:08:01.760 回答
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
于 2014-03-06T14:05:21.283 回答
2

使用辅助函数来包装 real open(),它调用 realopen()或返回具有方法的对象write()flush()并且close()

class MockFile(object):
    def write(self, data): pass
    def flush(self): pass
    def close(self): pass

def optionalWrite(filename, mode):
    if writeForRead: # <--- Your condition here
        return open(filename, mode)

    return MockFile()

with optionalWrite as output:
    ...
于 2014-03-06T14:05:26.287 回答
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
于 2014-03-08T05:15:58.507 回答
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
于 2014-03-06T14:56:17.550 回答
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被视为一个普通的文件对象;您需要将方法反射或委托给它。这样做,如果没有写入结果,则不会创建文件,但即使写入了一个结果,也会创建并写入文件。

于 2014-03-06T14:05:19.027 回答