Возможно ли изменить объект, на который ссылается оператор with, но сохранить функциональность контекста?

#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()  

Это соответствует всем требованиям. Все еще существует вероятность того, что во время создания экземпляра возникнет исключение, но оно все равно существует.

Мне все еще интересно узнать, есть ли у кого-нибудь лучшая альтернатива.