Временное изменение значения переменной в Python

Python 3.4 предоставляет этот удобный инструмент для временного перенаправления stdout:

# From https://docs.python.org/3.4/library/contextlib.html#contextlib.redirect_stdout
with redirect_stdout(sys.stderr):
    help(pow)

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

class redirect_stdout:
    def __init__(self, new_target):
        self._new_target = new_target
        # We use a list of old targets to make this CM re-entrant
        self._old_targets = []

    def __enter__(self):
        self._old_targets.append(sys.stdout)
        sys.stdout = self._new_target
        return self._new_target

    def __exit__(self, exctype, excinst, exctb):
        sys.stdout = self._old_targets.pop()

Мне интересно, есть ли общий способ использования оператора with для временного изменения значения переменной. Два других варианта использования из sys — это sys.stderr и sys.excepthook.

В идеальном мире сработало бы что-то вроде этого:

foo = 10
with 20 as foo:
    print(foo) # 20
print (foo) # 10

Я сомневаюсь, что мы сможем это сделать, но, возможно, что-то вроде этого возможно:

foo = 10
with temporary_set('foo', 20):
    print(foo) # 20
print (foo) # 10

Я могу заставить это работать, копаясь в globals(), но никто не захочет использовать это.

ОБНОВЛЕНИЕ: хотя я думаю, что мои примеры «foo = 10» прояснили, что я пытаюсь сделать, они не передают реальный вариант использования. Вот два:

  1. Перенаправить stderr, очень похоже на redirect_stdout
  2. Временно измените sys.excepthook. Я много разрабатываю в интерактивном режиме, и когда я добавляю что-то в excludehook (заключая исходную функцию в одну из своих собственных, скажем, для регистрации исключений с помощью модуля ведения журнала), я обычно хочу, чтобы это было удалено в какой-то момент. Таким образом, у меня не будет все больше и больше копий моей функции, обертывающей саму себя. Этот вопрос сталкивается с тесно связанной проблемой.

person kuzzooroo    schedule 11.05.2014    source источник
comment
Можешь мотивировать такую ​​идиому? В какой ситуации это было бы полезно?   -  person Dr. Jan-Philip Gehrcke    schedule 12.05.2014


Ответы (5)


Основываясь на ответе @arthaigo, более краткая версия:

import contextlib

@contextlib.contextmanager
def temporary_assignment(object, new_value):
  old_value = eval(object)
  globals()[object] = new_value
  yield
  globals()[object] = old_value
person Hugues    schedule 26.07.2019
comment
К сожалению, ни одно из этих решений не может работать с разными модулями. Так что этот temporary_assignment нельзя поместить в библиотечный модуль и использовать в другом месте, потому что у него не будет доступа к globals() в вызывающем модуле. См. также stackoverflow.com/questions /41454678/ - person Hugues; 07.04.2021

Я знаю, что этот вопрос довольно старый, но когда я столкнулся с той же проблемой, вот мое решение:

class test_context_manager():
    def __init__(self, old_object, new_object):
        self.new = new_object
        self.old = old_object
        self.old_code = eval(old_object)
    def __enter__(self):
        globals()[self.old] = self.new
    def __exit__(self, type, value, traceback):
        globals()[self.old] = self.old_code

Это не красиво, так как сильно использует глобальные переменные, но, похоже, работает.

Например:

x = 5
print(x)
with test_context_manager("x", 7):
    print(x)

print(x)

Результат:

5
7
5

или с функциями:

def func1():
    print("hi")

def func2():
    print("bye")

x = 5
func1()
with test_context_manager("func1", func2):
    func1()

func1()

Результат:

hi
bye
hi
person arthaigo    schedule 24.09.2015
comment
Хороший. Я думаю, вы можете получить что-то более общее, если будете использовать exec("{} = {}".format(self.old, repr(self.new)), globals()). С этим изменением я также могу обрабатывать строки. - person kuzzooroo; 24.09.2015
comment
Хороший. Это полезное дополнение. Я тестировал его только с функциями и целыми числами. - person arthaigo; 24.09.2015
comment
Полностью избавившись от exec, он стал еще более общим. Теперь можно передать произвольный вторичный объект. - person arthaigo; 02.10.2015


Обычно я использую этот пользовательский менеджер контекста attr_as:

from contextlib import contextmanager

@contextmanager
def attr_as(obj, field:str, value) -> None:
    old_value = getattr(obj, field)
    setattr(obj, field, value)
    yield
    setattr(obj, field, old_value)

Затем вы можете использовать его с тем же набором аргументов, что и для setattr в attr_as.

class Foo:
    def __init__(self):
        self.x = 1

foo = Foo()
with attr_as(foo, 'x', 2):
    print(foo.x)

bar = 3
with attr_as(sys.modules[__name__], 'bar', 4):
    print(bar) 

Обратите внимание: если вам нужно сохранить существование/отсутствие атрибута, а не только значение, это также возможно, добавив еще несколько строк:

from contextlib import contextmanager

@contextmanager
def attr_as(obj, field:str, value) -> None:
    old_exists = hasattr(obj, field)
    if old_exists:
        old_value = getattr(obj, field)
    setattr(obj, field, value)
    yield
    if old_exists:
        setattr(obj, field, old_value)
    else:
        delattr(obj, field)
person AJMansfield    schedule 02.03.2021

Как насчет closure?

i.e.

#!/usr/bin/python

def create_closure(arg):
    var = arg

    def hide_me():
        return var

    return hide_me


x = 10
clo = create_closure(x)

x *= 2
x2 = clo()
x += 1

print "x: {0}".format(x)
print "x2: {0}".format(x2)

Это дает:

x: 21
x2: 10

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

person khampson    schedule 11.05.2014
comment
Интересно. Что достигается закрытием, чего вы не могли сделать с временной переменной, как предлагает @arshajii? Другими словами, вместо clo = create_closure(x) просто поставить tmp = x? - person kuzzooroo; 12.05.2014
comment
Что ж, будучи замыканием, оно имеет то преимущество, что его можно носить повсюду: передавать в другие функции, области видимости и т. д., и оно всегда будет иметь это значение. Это, вероятно, более полезно, если вы используете его не только для простого int. то есть для хранения состояния, содержащего несколько разных переменных, структур и т. д. Вы также можете создать более одной из них для разных наборов значений, что опять же может пригодиться при хранении состояний в разных точках и подобных вещах. (Мой пример выше придуман, чтобы проиллюстрировать общую концепцию.) - person khampson; 12.05.2014
comment
Представьте себе замыкание, содержащее, скажем, 10 различных переменных, представляющих состояние, и скажите, что вы хотите иметь 4 разных набора переменных, представляющих различные точки в вашем приложении. Вы можете вызывать их в любой момент, передавать их, хранить в диктофоне с указанием времени или чего-то еще и т. д. Все это гораздо более выразительно и легче отслеживается, чем куча отдельно объявленных локальных переменных. Закрытие, по сути, представляет собой своего рода фрейм стека в этом типе использования. - person khampson; 12.05.2014