Можно ли условно использовать выражение "с"?

У меня есть функция 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" условно легко, но есть ли способ сделать создание файла зависимым от оператора "с"? (Я знаю, что я мог бы обрабатывать открытие и закрытие файла явно, но я бы взял накладные расходы "try/finally", чтобы оператор "с" был создан, чтобы этого избежать.)

Есть ли элегантное решение для этого?

Отредактировано для добавления: Я мог бы упростить пример. Вместо записи в произвольный файл я использую matplotlib.backends.backend_pdf.PdfPages (ссылка) и добавление сюжета (новая страница в PDF) в каждый шаг. В частности, это означает, что я не могу повторно открыть PDF файл с помощью PdfPages, потому что он будет перезаписан.

Ответ 1

Вы можете написать свою собственную функцию контекстного менеджера:

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

Ответ 2

Похоже, вам нужно передать функцию в область, которая инкапсулирует тот факт, что вы можете или не хотите хранить результаты в файле. В языках OO это будет называться шаблоном Strategy, но в 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

Ответ 3

Используйте вспомогательную функцию для переноса реального open(), который либо вызывает реальный open(), либо возвращает объект с методами 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:
    ...

Ответ 4

Использование сопрограмм

http://www.dabeaz.com/coroutines/Coroutines.pdf (как было предложено Пауло Сардин)

Если мы хотим написать:

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

Большинство, если не все, других ответов описывают, как написать менеджер контекста, который позволит вам делать то, что вы хотите. Здесь что-то, что непосредственно обращается к вашему вопросу.

Да, вы можете — с помощью функции генератора в сочетании с циклом for, который выполняется только один раз, чтобы избежать использования контекста with (и любая связанная обработка файлов). Ваша функция write_result() должна проверить свой аргумент output, чтобы увидеть, должна ли она что-либо делать, или вы можете проверить значение output перед ее вызовом.

def conditionally_with(filename=None, mode='r'):
    if not filename:
        yield None
    else:
        with open(filename, mode) as opened:
            yield opened

results = []
for output in conditionally_with(filename, "w"):
    for item in items:
        result = compute_stuff(item)
        write_result(item, result, output)
        results.append(result)
return results

Ответ 6

Здесь что-то вытекает из предложения wheaties ', который, я думаю, может быть лучшим методом без контекста-менеджера (и поэтому заслуживает пример кода, который иллюстрирует его больше конкретно):

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

Ответ 7

То, что вы пытаетесь сделать, это позднее создание файла. То, что вы хотите, это то, что выглядит как менеджер контекста, но на самом деле не создает файл, пока вам это не понадобится. Вам нужно будет реализовать __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 как обычный файловый объект; вам нужно будет отразить или передать методы до этого. Если это не будет сделано, файл не будет создан, но если будет записан хотя бы один результат, файл будет создан и записан в.