#python #with-statement
#питон #с-заявлением
Вопрос:
Я пытаюсь найти способ использовать with
инструкцию для открытия одного из нескольких больших файлов базы данных. Для целей этого вопроса можно предположить, что мы хотим убедиться, что файл будет закрыт, как только он больше не понадобится, поскольку существует ограничение на количество файлов базы данных, которые могут быть открыты одновременно базовой библиотекой.
Файлы базы данных считываются, скажем , с помощью определенного класса Database
, который не считывает все содержимое базы данных при создании экземпляра, а вместо этого просто считывает метаданные о базе данных. Эти метаданные используются для определения того, является ли база данных допустимой или нет. (К вашему сведению) Только в том случае, если запрашиваются определенные данные, он дополнительно запрашивает (открытый) файл.
Вот минимальный пример того, как это может быть закодировано без with
контекста:
file='file1.dh' # don't read into the extension; it's just an example dataobj=Database(file) # Database was created specifically to read files of .dh extension if not dataobj.isvalid: dataobj.close() file='file2.dh' dataobj=Database(file) # ... then proceed with remainder of calculation # and finally... databoj.close()
У Database
класса есть необходимые __enter__
и __exit__
методы, позволяющие использовать его в with
конструкции, и это желательно. Например (псевдокод):
class Database(): def __init__(self,file): self.read_metadata() def read_metadata(self): # ... read metadata from file def close(self): # ... do some necessary cleanup and disconnect from underlying database reader def __enter__(self): return self def __exit__(self,typ,value,traceback): self.close()
Реальная мотивация этого вопроса заключается в том, что, поскольку даже чтение метаданных из этих больших файлов занимает много времени, очень нежелательно (1) открывать первый допустимый файл, найденный более одного раза, или (2) открывать больше файлов, чем необходимо (т. Е. Ресурсы не должны тратиться на открытие нескольких файлов).
Есть надежда, что, как только будет найден допустимый файл, мы сможем продолжить работу с этим файлом, открытым в with
контексте. В целом можно видеть, что это может быть распространено на неопределенное количество файлов, но в этом нет необходимости для данного вопроса.
Например, используя with
оператор, мы могли бы написать
with Database('file1.dh') as dh: if dh.isvalid: file='file1.dh' else: file='file2.dh' with Database(file) as dh: # ...proceed with calculation
Для этого требуется, чтобы file1.dh
он открывался дважды, когда он действителен.
Можно ли этого избежать, все еще используя with
оператор? Пожалуйста, также не стесняйтесь предлагать что-то, о чем я, возможно, не знаю (т. Е. Я не хочу исключать возможные подходы, которые не используют with
, но все еще сохраняют функциональность контекстного менеджера).
Я брошу в дело еще один гаечный ключ… на данный момент второй файл «file2.dh» неизвестен (он вычислен), и предпочтительно сохранить его таким образом (т. е. Некоторые вычисления можно сохранить, не зная даже идентичности второго файла, если он не нужен.. если первый файл будет признан допустимым).
Я приведу здесь пример псевдокода, концептуально описывающего то, чего я хотел бы достичь:
with FirstValidFile('file1.dh','file2.dh') as dh: # ... do all computation
где file2.dh
заменяет dh, если file1.dh
это недопустимо, в противном случае расчет продолжается file1.dh
.
Я полагаю, что следующее сработало бы, но идеально ли это, или есть другой способ? Есть ли способ сохранить with
конструкцию вокруг Database
объектов по отдельности, чтобы в случае исключения была произведена очистка?
class FirstValidFile(): def __init__(self,*potential_files): for file in potential_files: dh=Database(file) if dh.isvalid: self.dh=dh return dh.close() def __enter__(self): return self.dh def __exit__(self,a,b,c): self.dh.__exit__(a,b,c)
Ответ №1:
Я бы просто добавил флаг и запустил его в while
цикл. Вы также можете выполнить бесконечный цикл с break
помощью оператора, если хотите, но я думаю, что он немного менее удобен в обслуживании.
file = 'file1.dh' valid_file_found= False while not valid_file_found: with Database(file) as dh: if dh.isvalid: # do calculations valid_file_found= True else: file = # calculate file name
Комментарии:
1. Вау. У меня действительно все было наоборот. Я бы предпочел
break
continue
подход/, но это просто для того, чтобы избежать лишних отступов. Спасибо.
Ответ №2:
Я думаю, что ответ на этот вопрос довольно прост: все, что вам нужно сделать, это поместить весь исходный код, определяющий выбранный объект базы данных, в функцию, которая возвращает «победителя». Поскольку это фактический объект контекстного менеджера, вы сохраните нужные свойства.
Как упрощенное понятие:
import random from dataclasses import dataclass @dataclass class Database: name: str def __enter__(self): print("__enter__", self) return self def __exit__(self, *args): print("__exit__", self) def chose_database(*args): return Database(random.choice(args)) with chose_database("foo", "bar", "baz") as db: print("the chosen db", db)
Комментарии:
1. Спасибо за ваш ответ, но суть здесь в функции, которая определяет, какой файл использовать. Таким образом, вам придется открыть файл дважды: один раз, чтобы определить, что он действителен, и еще раз после этого.
Ответ №3:
После дальнейшего рассмотрения я предложу ответ в качестве дополнения к решению, которое я предложил в своем вопросе. Используйте наследование от Database
класса, чтобы полностью связать конечное возвращаемое значение __enter__
с требуемым Database
объектом, но через super
функцию.
class FirstValidFile(Database): def __init__(self,*potential_files): for file in potential_files: super().__init__(self,file) if self.isvalid: return self.close()
Это соответствует всем требованиям. Все еще существует вероятность того, что во время создания экземпляра возникнет исключение, но оно все равно существует.
Мне все еще интересно узнать, есть ли у кого-нибудь лучшая альтернатива.