Типично требовать для некоторой задачи нескольких объектов, у которых есть ресурсы, которые должны быть явно выпущены - скажем, два файла; это легко сделать, когда задача является локальной для функции с использованием вложенных with
блоками, или - еще лучше - один with
блоком с несколькими with_item
пунктов:
with open('in.txt', 'r') as i, open('out.txt', 'w') as o:
# do stuff
OTOH, я все еще пытаюсь понять, как это должно работать, когда такие объекты не только локальны для области функций, но принадлежат экземпляру класса - другими словами, как создаются менеджеры контекста.
В идеале я хотел бы сделать что-то вроде:
class Foo:
def __init__(self, in_file_name, out_file_name):
self.i = WITH(open(in_file_name, 'r'))
self.o = WITH(open(out_file_name, 'w'))
и Foo
сам превращается в менеджер контекста, который обрабатывает i
и o
, так что когда я это делаю
with Foo('in.txt', 'out.txt') as f:
# do stuff
self.i
и self.o
заботятся автоматически, как и следовало ожидать.
Я возился о написании таких вещей, как:
class Foo:
def __init__(self, in_file_name, out_file_name):
self.i = open(in_file_name, 'r').__enter__()
self.o = open(out_file_name, 'w').__enter__()
def __enter__(self):
return self
def __exit__(self, *exc):
self.i.__exit__(*exc)
self.o.__exit__(*exc)
но он как подробный, так и небезопасный в отношении исключений, возникающих в конструкторе. После некоторого времени поиска я нашел этот блог-блог в 2015 году, в котором используется contextlib.ExitStack
чтобы получить что-то очень похожее на то, что мне нужно:
class Foo(contextlib.ExitStack):
def __init__(self, in_file_name, out_file_name):
super().__init__()
self.in_file_name = in_file_name
self.out_file_name = out_file_name
def __enter__(self):
super().__enter__()
self.i = self.enter_context(open(self.in_file_name, 'r')
self.o = self.enter_context(open(self.out_file_name, 'w')
return self
Это довольно удовлетворительно, но я озадачен тем, что:
- Я ничего не знаю об этом использовании в документации, поэтому он, похоже, не является "официальным" способом решения этой проблемы;
- в общем, мне очень сложно найти информацию об этой проблеме, что заставляет меня думать, что я пытаюсь применить непитологическое решение проблемы.
Некоторый дополнительный контекст: я работаю в основном в C++, где нет разницы между случаем блочной области и случаем объектной области для этой проблемы, так как эта очистка реализована внутри деструктора (подумайте __del__
, но вызывается детерминистически), а деструктор (даже если он явно не определен) автоматически вызывает деструкторы подобъектов. Итак, оба:
{
std::ifstream i("in.txt");
std::ofstream o("out.txt");
// do stuff
}
а также
struct Foo {
std::ifstream i;
std::ofstream o;
Foo(const char *in_file_name, const char *out_file_name)
: i(in_file_name), o(out_file_name) {}
}
{
Foo f("in.txt", "out.txt");
}
сделайте всю очистку автоматически, как вы обычно этого хотите.
Я ищу аналогичное поведение в Python, но, опять же, я боюсь, что я просто пытаюсь применить шаблон, исходящий из C++, и что основная проблема имеет радикально другое решение, о котором я не могу думать,
Итак, чтобы подвести итог: что такое решение __enter__
с тем, что объект, которому принадлежат объекты, требующие очистки, становится самим менеджером контекста, правильно вызывая __enter__
/__exit__
своих детей?