Контекстный менеджер Python для временного назначения переменных

Мне часто нужно временно заменить значение переменной чем-то другим, выполнить некоторые вычисления, которые зависят от этой переменной, а затем восстановить исходное значение переменной. Например:

var = 0
# Assign temporary value and do computation
var_ori = var
var = 1
do_something_with_var()  # Function that reads the module level var variable
# Reassign original value
var = var_ori

Это кажется очевидной возможностью использования диспетчера контекста (оператор with). Содержит ли стандартная библиотека Python такой менеджер контекста?

Редактировать

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

В моем реальном рабочем случае я не могу изменить функцию do_something_with_var. На самом деле это даже не функция, а строка кода, которая оценивается в контексте глобального пространства имен как часть некоторого метапрограммирования. Пример, который я привел, был самым простым, который я мог придумать, который решил мою проблему с временной переменной. Я не просил обходной путь (правильную версию) моего примера кода, а просил получить ответ на свой письменный вопрос.


person jmd_dk    schedule 04.01.2017    source источник
comment
Я решил не голосовать по этому вопросу, но я бы сказал, что если вам часто нужна такая конструкция, необходимо решить более серьезные проблемы с дизайном.   -  person NPE    schedule 04.01.2017
comment
Предыдущий ответ: stackoverflow.com/questions/3024925/   -  person Bill Schumacher    schedule 04.01.2017
comment
@BillSchumacher: Что? Насколько этот вопрос актуален?   -  person user2357112 supports Monica    schedule 04.01.2017
comment
как это не по теме? любой, кто использует jupyter, постоянно сталкивается с этой ситуацией. я бы хотел, чтобы менеджер контекста, такой как with Namespace() as ns:, имел небольшое закрытие или что-то в этом роде, а затем возвращался в родительское пространство имен после выполнения. (я не компьютерный ученый - кто-нибудь поправьте меня, если я не правильно использую эти слова)   -  person grisaitis    schedule 17.10.2019
comment
Это отличный вопрос. Почему его закрыли?? В нем говорится, что нужны детали отладки, но с редактированием совершенно ясно (по крайней мере, мне), каково желаемое поведение. Абсурд! Пожалуйста, либо оставьте его открытым, либо предоставьте какое-нибудь последовательное обоснование.   -  person Ben Mares    schedule 30.05.2020


Ответы (3)


Нет, потому что диспетчер контекста не может так назначать переменные в области действия вызывающего объекта. (Любой, кто думает, что вы можете сделать это с помощью locals или inspect, попробуйте использовать диспетчер контекста, который вы придумали внутри функции. Это не сработает.)

Существуют существуют утилиты для работы с вещами, не являющимися локальными переменными, такими как глобальные переменные модулей, другие атрибуты объектов и словари... но они unittest.mock.patch и связанные с ним функции, поэтому вам следует тщательно рассмотреть другие альтернативы, прежде чем использовать их в не- контекст тестирования. Такие операции, как «временно изменить эту вещь, а затем восстановить ее», как правило, приводят к запутанному коду и могут указывать на то, что вы используете слишком много глобального состояния.

person user2357112 supports Monica    schedule 04.01.2017
comment
Я действительно думал, что что-то вроде inspect понадобится, если я сам напишу менеджер контекста, что слишком уродливо, чтобы я мог об этом беспокоиться. Но почему это должно быть даже невозможно? with как-то скрывает стеки функций? - person jmd_dk; 04.01.2017
comment
@jmd_dk: Нет, просто Python не предоставляет доступ к структурам данных, которые содержат локальные переменные функции. - person user2357112 supports Monica; 04.01.2017
comment
@jmd_dk - Это невозможно, потому что в этом нет необходимости. Функции работают с глобальными значениями таким образом, что не влияют на эти значения, принимая их в качестве параметров (и при необходимости создавая копию внутри функции). - person TigerhawkT3; 04.01.2017

Простой ответ на ваш вопрос:

Содержит ли стандартная библиотека Python такой менеджер контекста?

это «Нет, это не так».

person NPE    schedule 04.01.2017
comment
Я почти уверен, что мы все знаем, что он тоже хочет работающее решение ;-) - person Christian Dean; 04.01.2017
comment
@leaf: Я думаю, будет справедливо сказать, что мы также знаем, что вопрос довольно ошибочен, поэтому предоставление работоспособных решений не обязательно конструктивно. ;-) - person NPE; 04.01.2017
comment
@NPE Я так устал от такого отношения. Половина вопроса (редактирование) занята бессмысленным подтверждением того, что я действительно хочу получить фактический ответ, а не быть отклоненным. У меня был технический вопрос, и я ожидал технического ответа. - person jmd_dk; 13.02.2019

Моя ошибка, вместо этого, возможно, что-то вроде этого, это не встроено:

class ContextTester(object):
    """Initialize context environment and replace variables when completed"""

    def __init__(self, locals_reference):
        self.prev_local_variables = locals_reference.copy()
        self.locals_reference = locals_reference

    def __enter__(self):
        pass

    def __exit__(self, exception_type, exception_value, traceback):
        self.locals_reference.update(self.prev_local_variables)



a = 5
def do_some_work():
    global a
    print(a)
    a = 8
print("Before context tester: {}".format(a))
with ContextTester(locals()) as context:
    print("In context tester before assignment: {}".format(a))
    a = 6
    do_some_work()
    print("In context tester after assignment: {}".format(a))
print("After context tester: {}".format(a))

Выход:

Before context tester: 5
In context tester before assignment: 5
6
In context tester after assignment: 8
After context tester: 5

Для ясности, чтобы вы знали, что он действительно что-то делает:

class ContextTester(object):
    """Initialize context environment and replace variables when completed"""

    def __init__(self, locals_reference):
        self.prev_local_variables = locals_reference.copy()
        self.locals_reference = locals_reference

    def __enter__(self):
        pass

    def __exit__(self, exception_type, exception_value, traceback):
        #self.locals_reference.update(self.prev_local_variables)
        pass

a = 5
def do_some_work():
    global a
    print(a)
    a = 8
print("Before context tester: {}".format(a))
with ContextTester(locals()) as context:
    print("In context tester before assignment: {}".format(a))
    a = 6
    do_some_work()
    print("In context tester after assignment: {}".format(a))
print("After context tester: {}".format(a))
a = 5
print("Before context tester: {}".format(a))
with ContextTester(locals()) as context:
    print("In context tester before assignment: {}".format(a))
    a = 6
    print("In context tester after assignment: {}".format(a))
print("After context tester: {}".format(a))

Выход:

Before context tester: 5
In context tester before assignment: 5
6
In context tester after assignment: 8
After context tester: 8

Before context tester: 5
In context tester before assignment: 5
In context tester after assignment: 6
After context tester: 6

Вы также можете сделать это:

def wrapper_function(func, *args, **kwargs):
    prev_globals = globals().copy()
    func(*args, **kwargs)
    globals().update(prev_globals)

Следует отметить, что если вы попытаетесь использовать оператор with внутри функции, вы захотите использовать globals() в качестве ссылки на локальные, и это может иметь непредвиденные последствия, в любом случае все еще может.

Я бы не рекомендовал делать это вообще, но должно работать.

person Bill Schumacher    schedule 04.01.2017
comment
Спасибо, но похоже, что вы по-прежнему вручную снова устанавливаете a = 5 после завершения работы. Кроме того, do_some_work должен только читать глобальный a, а не переназначать его. - person jmd_dk; 04.01.2017
comment
Нижний пример должен был показать нормальную работу Python без сброса переменных. - person Bill Schumacher; 04.01.2017