Обернуть Python с заявлением из 2 классов в одном классе - PullRequest
0 голосов
/ 10 октября 2018

У меня есть класс Python A с оператором with и класс B с оператором with.Теперь это работает так:

with A() as a:
    with B() as b:
        do_things()

Как мне построить класс C, который обернет класс A и класс B, чтобы я мог назвать его так:

 with C() as c:
        do_things()

имеет одинаковые функции

Ответы [ 3 ]

0 голосов
/ 10 октября 2018

Краткий ответ : возможно возможно .Но контекстные менеджеры позволяют реализовать некоторую логику, что делает «сложным» реализовать ее абсолютно правильным образом.Ниже вы видите «Подтверждение концепции», но я не гарантирую, что он имеет точно такое же поведение.Поэтому я действительно советую работать с вложенными with s.

То, что здесь не рассматривается : __init__ или __enter__ также могут вызывать исключения, и тогда ониобрабатываются «внешними» контекстными менеджерами.Это делает это, конечно, довольно сложным.По сути, вам нужно «собрать» стек в __enter__, а затем «вытолкнуть» стек в случае сбоя одного из __enter__.Этот сценарий не описан здесь.

Мы можем создать «составной» контекстный менеджер:

class C:

    def __init__(self, *ctxs):
        self.ctxs = ctxs

    def __enter__(self):
        return tuple(ctx.__enter__() for ctx in self.ctxs)

    def __exit__(self, self, exception_type, exception_value, traceback):
        for ctx in reversed(self.ctxs):
            try:
                if ctx.__exit__(exception_type, exception_value, traceback):
                    (exception_type, exception_value, traceback) = (None,) * 3
            except Exception as e:
                exception_value = e
                traceback = e.__traceback__
                exception_type = type(e)
        return exception_value is None

Часть __exit__ сложна.Прежде всего, нам нужно выйти в обратном порядке.Но обработка исключений еще более сложна: если __exit__ заставляет исключение замолчать, возвращая «правдивое» значение, то мы должны передать (None, None, None) как (exception_type, exeption_value, traceback), но проблема, которая может возникнуть, заключается в том, что __exit__с другой стороны, запускает само исключение и, следовательно, вводит новое исключение.

Затем мы можем использовать процессор контекста, например:

with C(A(), B()) as (a,b):
    # ...
    pass

Таким образом, приведенное выше позволяет реализовать менеджер контекста.для произвольного числа «субконтекст-менеджеров».Мы можем создать подкласс этого класса, например:

class ContextAB(C):

    def __init__(self):
        super(ContextAB, self).__init__(A(), B())

, а затем использовать его как:

with ContextAB() as (a, b):
    # ...
    pass

Но вкратце: используйте вложенные операторы with.Это также делает более ясным, что здесь происходит.Прямо сейчас C инкапсулирует все виды логики, которые лучше сделать явными.Если ввод B завершится неудачно, то это должно привести к исключению, которое обрабатывается __exit__ из A и т. Д. Это делает очень трудным получение "деталей", полностью эквивалентныхсемантика оператора with.

0 голосов
/ 10 октября 2018

Если ваша причина для этого состоит в том, чтобы уменьшить отступ или объединить операторы with, вам не нужно.Вы можете просто

with A() as a, B() as b:
    ...

ввести несколько менеджеров контекста в одной строке.

Если у вашего C есть другие причины для существования, вам нужно быть осторожным при рассмотрении случая, когда одинменеджер контекста завершается ошибкой после того, как другой менеджер контекста был создан и / или введен.contextlib.ExitStack может помочь вам реализовать это надежно, или contextlib2.ExitStack, если вы все еще на Python 2:

import contextlib

class C(object):
    def __enter__(self):
        with contextlib.ExitStack() as stack:
            stack.enter_context(A())
            stack.enter_context(B())
            self._stack = stack.pop_all()
        return self
    def __exit__(self, exc_type, exc_value, exc_tb):
        return self._stack.__exit__(exc_type, exc_value, exc_tb)
0 голосов
/ 10 октября 2018

Я хотел бы предложить альтернативу.Вы можете инициализировать как a, так и b в одной строке:

with A() as a, B() as b:
    do_things()

Это более сжато и уменьшает количество отступов в глубоко вложенном коде.

Однако, если вам абсолютно необходимо использовать класс, переопределите методы __enter__ и __exit__:

class C:
    def __enter__(self):
        self._a = A()
        self._b = B()
        return (self._a.__enter__(), self._b.__enter__())

    def __exit__(self ,type, value, traceback):
        # Cleanup code here.
        self._b.__exit__(type, value, traceback)
        self._a.__exit__(type, value, traceback)

И затем используйте C внутри диспетчера контекста, например:

with C() as (a, b):
    do_things()

Если вы не хотите ссылаться на a и b, или если вы не планируете ничего с ними делать, тогда

with C():
    do_things()

Будет такжеРабота.Этого должно быть достаточно, чтобы начать, но, пожалуйста, обратите внимание, что есть недостатки, любезно упомянутые пользователями в комментариях.Основным из них является то, что если self._b.__enter__ выдает ошибки, self._a.__enter__ необходимо будет очистить (это можно сделать с помощью try-исключением-finally).Кроме того, некоторые менеджеры контекста могут нуждаться в различном подходе в зависимости от того, каким ресурсом управляют.

...