Как написать пустой (no-op) contextmanager в Python?

Иногда мне нужен фиктивный менеджер контекста, который ничего не делает. Затем он может использоваться в качестве резервной копии для более полезного, но необязательного диспетчера контекстов. Например:

ctx_mgr = <meaningfulContextManager> if <condition> else <nullContextManager>
with ctx_mgr:
    ...

Как определить такой тривиальный пустой менеджер контекста? Является ли библиотека Python одной с полки?

Как насчет случаев, когда мы хотим, чтобы контекст использовался с предложением as?

with ctx_mgr as resource:
    <operations on resource>

Ответ 1

Python 3.7 и выше: используйте contextlib.nullcontext, специально разработанный для этой contextlib.nullcontext.

До Python 3.7 стандартная библиотека не предлагала диспетчера контекста, специально разработанного для этих случаев использования, но есть некоторые обходные пути.

Начиная с Python 3.4, contextlib.suppress может использоваться для этой цели в первом случае, то есть, когда нет предложения as:

ctx_mgr = <meaningfulContextManager> if <condition> else contextlib.suppress()

with ctx_mgr:
    ...

Начиная с Python 3.3, аналогичный contextlib.ExitStack также доступен, contextlib.ExitStack, хотя и медленнее, чем suppress (в моих тестах это занимает вдвое больше времени).

До Python 3.3 или в случае, если вам нужно предложение as до Python 3.7, разработчики должны сами выпустить. Вот одна из возможных реализаций (см. Примечание внизу, но все ошибки мои):

class NullContextManager(object):
    def __init__(self, dummy_resource=None):
        self.dummy_resource = dummy_resource
    def __enter__(self):
        return self.dummy_resource
    def __exit__(self, *args):
        pass

Тогда можно написать:

ctx_mgr = <meaningfulContextManager> if <condition> else NullContextManager(dummy_resource)

with ctx_mgr as resource:
    <operations on resource>

Конечно, dummy_resource должен поддерживать все операции, необходимые для "значимого" ресурса. Так, например, если содержательный менеджер контекста в __enter__() возвращает что-то, что сделано для quack() внутри управляемого блока, dummy_resource также должен будет поддерживать это, хотя, возможно, вообще ничего не делая.

class DummyDuck(object):
    def quack()
        # Ssssh...
        pass

ctx_mgr = <meaningfulContextManager> if <condition> else NullContextManager(DummyDuck())

with ctx_mgr as someDuck:
    someDuck.quack()

Источник: запрос функции Python. Большое спасибо всем, кто внес вклад в эту дискуссию. Это моя попытка подвести итоги своего вопроса в ответе на вопрос, чтобы сэкономить время, читая эту длинную ветку. Также см. В документации по Python упоминание об этом использовании ExitStack.

Ответ 2

Начиная с Python 3.2, memoryview(b'') можно использовать в качестве memoryview(b'') диспетчера контекста. См. Https://docs.python.org/3/library/stdtypes.html#memoryview.release.

Pros

  • Импорт не требуется

  • Работает на 3. 2+

  • Примерно в два раза быстрее, чем contextlib.nullcontext

Cons

  • Вы, вероятно, хотите добавить комментарий # no-op.

Ответ 3

Простое решение для Python 3.6 и ниже, включая 2.7:

from contextlib import contextmanager

@contextmanager
def nullcontext(enter_result=None):
    yield enter_result

Начиная с Python 3.7, вы должны использовать предоставленный contextlib.nullcontext.

Ответ 4

Я просто использовал threading.Lock() как фиктивный менеджер контекста. Временная блокировка, используемая только диспетчером контекста.