Имеет ли Python эквивалент функций Haskell "mask" или "bracket"?

В Haskell у нас есть асинхронные исключения; мы можем использовать throwTo, чтобы поднять любое исключение в другом потоке:

throwTo :: Exception e => ThreadId -> e -> IO ()

throwTo вызывает произвольное исключение в целевом потоке (только GHC).

Чтобы иметь возможность писать код с такими гарантиями, как "всегда будет выходить из замка после его приобретения", у нас mask для запуска код, в котором асинхронные исключения могут приниматься только при блокировке вычислений:

mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b

Выполняет вычисление ввода-вывода с асинхронными исключениями в масках. То есть любой поток, который пытается поднять исключение в текущем потоке с помощью throwTo, будет заблокирован до тех пор, пока асинхронные исключения не будут снова разобраны.

и более сильный uninterruptibleMask, в котором исключения async вообще не будут возникать при маскированном вычислении:

uninterruptibleMask :: ((forall a. IO a -> IO a) -> IO b) -> IO b

Подобно mask, но маскированное вычисление не прерывается

Маскировка используется для реализации абстракций более высокого уровня, таких как bracket:

bracket
    :: IO a         -- computation to run first ("acquire resource")
    -> (a -> IO b)  -- computation to run last ("release resource")
    -> (a -> IO c)  -- computation to run in-between
    -> IO c         -- returns the value from the in-between computation

Когда вы хотите приобрести ресурс, выполните некоторую работу с ним, а затем отпустите ресурс, рекомендуется использовать bracket, потому что bracket установит необходимый обработчик исключений, чтобы освободить ресурс в что исключение возникает при вычислении. Если возникает исключение, то bracket будет повторно поднимать исключение (после выполнения выпуска).

Если я правильно понимаю, Python имеет (менее общую) форму асинхронных исключений, причем наиболее заметным проявлением является KeyboardInterrupt:

Поднимается, когда пользователь нажимает клавишу прерывания (обычно Control - C или Delete). Во время выполнения проверка прерываний выполняется регулярно.

Документация неточна, когда может произойти "проверка прерываний", но, похоже, подразумевается, что KeyboardInterrupt может быть поднят в любой момент выполнения программы. Таким образом, кажется, что исключения на асинхронном языке Python приходят со всеми теми же трудностями, что и при сохранении правильности.

Например, рассмотрим такой шаблон:

x = None
try:
    x = acquire()
    do_something(x)    # (1)
finally:
    if x is not None:  # (2)
        release(x)

Если какое-либо исключение возникает во время (1), то мы уверены, что будет выполняться содержимое блока finally. Но что произойдет, если a KeyboardInterrupt находится во время (2)?

Кажется принципиально невозможным гарантировать очистку ресурсов при наличии исключений asyc без способа их маскировки. Есть ли какое-то средство для этого, или мы полагаемся на страусный алгоритм?

Ответ 1

Это контекстные менеджеры.

with acquire() as x:
    do_something(x)

acquire возвращает объект, тип которого определяет метод __enter__, который возвращает значение, привязанное к x, и метод __exit__, который выполняется в конце инструкции with независимо от того, как утверждение завершено.