Можно ли создать контекстно-зависимый менеджер контекста Python, который сохраняет, изменяет и восстанавливает состояние?

У меня есть пара функций Python, которые в настоящее время переворачивают глобальную переменную между двумя значениями. Я хотел бы превратить их в контекстные менеджеры, чтобы я мог использовать их как блоки with, устанавливая переменную внутри блока, но восстанавливая ее после. Вот желаемое поведение:

>>> MODE
'user'
>>> mode_sudo()  # Sets MODE to 'sudo'...
>>> MODE
'sudo'
>>> mode_user()  # Sets MODE to 'user'...
>>> MODE
'user'
>>> with mode_sudo():
...    print MODE
'sudo'
>>> MODE
'user'

Возможна ли такая химера?

ОБНОВЛЕНИЕ: Просто для ясности вот реализация контекстного менеджера:

from contextlib import contextmanager

@contextmanager
def mode_sudo():
    global MODE
    old_mode = MODE
    MODE = 'sudo'
    yield
    MODE = old_mode

@contextmanager
def mode_user():
    global MODE
    old_mode = MODE
    MODE = 'user'
    yield
    MODE = old_mode

Вызов их без ключевого слова with возвращает генератор. Есть ли способ получить поведение переключения режимов как с вызовом простой ванильной функции, так и с менеджером контекста шоколада?


person David Eyk    schedule 08.08.2011    source источник
comment
Какой смысл проверять значение MODE?   -  person Evpok    schedule 09.08.2011
comment
Не много, я полагаю. Пограничный случай испаряется при внимательном рассмотрении. :П   -  person David Eyk    schedule 09.08.2011
comment
Я удалил глупый тест ценности.   -  person David Eyk    schedule 09.08.2011
comment
А как насчет def set_mode(mode):, а потом MODE = mode? Таким образом, вам нужно получить код только один раз...   -  person glglgl    schedule 09.08.2011
comment
В некоторых случаях это может быть лучше, но в данном случае я пытаюсь встроить контекстные менеджеры в существующую кодовую базу с обратной совместимостью.   -  person David Eyk    schedule 10.08.2011


Ответы (2)


Сделай это так:

class mod_user:

    def __init__(self):
        global MODE
        self._old_mode = MODE
        MODE = "user"

    def __enter__(self):
        pass

    def __exit__(self, *args, **kws):
        global MODE
        MODE = self._old_mode

MODE = "sudo"

with mod_user():
    print MODE  # print : user.

print MODE  # print: sudo.

mod_user()
print MODE   # print: user.
person mouad    schedule 08.08.2011
comment
Именно то, что я искал. И никакой зависимости от contextlib. Это мило. :) - person David Eyk; 09.08.2011
comment
@David: Рад, что это было полезно :-) - person mouad; 09.08.2011
comment
Коварно, но приятно, я никогда не думал использовать конструктор для имитации вызываемого объекта. - person Evpok; 09.08.2011
comment
Наверное, не стоит делать это постоянно, но в данном случае эффект определенно того стоит. - person David Eyk; 09.08.2011
comment
Что касается именования класса диспетчера контекста mod_user вместо ModUser, похоже, не существует строго соблюдаемого соглашение об именах для классов диспетчера контекста. Но кажется более питоническим написать: with ModUser() as mod_user: ... - person smci; 12.04.2018

Простой способ:

from contextlib import contextmanager
@contextmanager
def mode_user():
    global MODE
    old_mode = MODE
    MODE = "user"
    yield
    MODE = old_mode

то же самое для mode_sudo(). Дополнительные сведения см. в документе. На самом деле это ярлык для всего «определить класс, который реализует __enter__ и __exit__».

person Evpok    schedule 08.08.2011
comment
Ой, я только что опубликовал редактирование, которое выглядит почти так же, как ваш код. :) Но это отвечает только на половину вопроса. Если вы просто вызовете эту реализацию без оператора with, вы получите генератор. - person David Eyk; 09.08.2011
comment
Дело в том, что когда вы выполняете with mode_user(), он вызывает метод __call__() mode_user, точно так же, как если бы вы просто вызывали mode_user() вне предложения with. Я не могу придумать, как определить, находитесь ли вы в предложении with. - person Evpok; 09.08.2011
comment
Мы оба были окружены contextlib и ярлыком его генератора. Реализация чистого протокола @mouad позволяет обойти это: __exit__ никогда не вызывается, кроме как в операторе with. - person David Eyk; 09.08.2011
comment
@David Дэвид, я так не понимаю - если я попробую указанный выше код и вызову mode_user(), я получу объект ‹contextlib.GeneratorContextManager›, который не является генератором, он даже не является итерируемым. Если вам не нравится такое поведение, вы можете, конечно, создать свой собственный класс, как описано выше. - person glglgl; 09.08.2011
comment
@glglgl Что ж, похоже, вы правы - это не повторяется. Тем не менее, основной пункт моего возражения остается в силе: ничего не происходит до тех пор, пока не будет вызван __enter__, что делает его бесполезным для данного конкретного случая использования. - person David Eyk; 10.08.2011
comment
Ааа, хорошо. Теперь при втором взгляде я вижу разницу: переключение режимов происходит в __init__(), а не в __enter__() здесь. Тогда понятно. Спасибо, что указали! - person glglgl; 10.08.2011