Остановить и завершить контекстный менеджер до его начала (но после его __init__)

#python #with-statement #contextmanager

#python #с помощью-statement #contextmanager

Вопрос:

У меня есть контекстный менеджер, подкласс родительского класса / context-manager, который я хотел бы завершить как можно скорее, если какое-то условие выполняется во время super().__init__ .

 class CM:
    def __init__(self, *args, **kwargs):
        print('__init__')
        super().__init__(*args, **kwargs)
        self.condition_depending_on_super_init_result = True  # set to true just to showcase the question
        if self.condition_depending_on_super_init_result: 
            self.__exit__()         # this has no impact: the lines after "with cm:" below are still executed!
            self = None             # why?
    def __enter__(self):
        print('__enter__')
    def __exit__(self, *args):
        print('__exit__')
    def dosomething(self, x):
        print(x)

cm = CM()
print('before with')
with cm:                         # here it should skip the block, not enter it, and not do the next lines
    print('after with')
    cm.dosomething('dosomething')
 

Комментарии:

1. Контекстные менеджеры не могут контролировать выполнение with тела.

Ответ №1:

Одним из достаточно очевидных обходных путей является создание исключения.

 import logging  # or whatever


class CMInitException(Exception):
    """
    Exception raised by CM.__init__() if the superclass fails to initialize.
    """
    pass


class CM:
    def __init__(self, *args, **kwargs):
        print('__init__')
        super().__init__(*args, **kwargs)
        self.condition_depending_on_super_init_result = True  # set to true just to showcase the question
        if self.condition_depending_on_super_init_result: 
            raise CMInitException("The moon over Redmond is not 3/4 full waxing")

    def __enter__(self):
        print('__enter__')
    def __exit__(self, *args):
        print('__exit__')
    def dosomething(self, x):
        print(x)

try:
    cm = CM()
    print('before with')
    with cm:
        print('after with')
        cm.dosomething('dosomething')
except CMInitException as e:
    logging.warning(e)
 

Комментарии:

1. Клянусь, я это придумал. У меня нет доступа к исходному коду Microsoft.

2. Ха-ха! Хорошее решение @tripleee! Изначально я хотел избежать дополнительного try except оператора /, видите ли вы решение, которое позволяет «пресечь cm в зародыше», чтобы оно with cm: немедленно возвращалось и не выполняло внутренние строки?

3. Если cm не инициализирован, вы не можете сделать with cm:

4. при вводе with ...: __enter__() выполняется, возможно ли в этот момент сразу пропустить весь блок внутри with ?

5. Аналогичным образом вы можете вызвать исключение в любой другой точке, если есть другая ситуация, когда вы хотите заставить вызывающего отказаться.