Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Temporarily changing a variable's value in Python

Python 3.4 provides this neat tool to temporarily redirect stdout:

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

The code is not super-complicated, but I wouldn't want to write it over and over again, especially since some thought has gone into it to make it re-entrant:

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()

I'm wondering if there's a general way to use the with statement to temporarily change the value of a variable. Two other use cases from sys are sys.stderr and sys.excepthook.

In a perfect world, something like this would work:

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

I doubt we can make that work, but maybe something like this is possible:

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

I can sort of getting this working by rooting around in globals(), but it's nothing anyone would choose to use.

UPDATE: while I think my "foo = 10" examples clarified what I'm trying to do, they do not convey an actual use case. Here are two:

  1. Redirect stderr, much like redirect_stdout
  2. Temporarily change sys.excepthook. I do a lot of development interactively, and when I add something to excepthook (by wrapping the original function in one of my own, say, to log exceptions using the logging module), I generally want it to get removed at some point. That way I won't have more and more copies of my function wrapping itself. This question confronts a closely related problem.
like image 938
kuzzooroo Avatar asked May 11 '14 20:05

kuzzooroo


People also ask

How do you change a variable's value?

In Variables view, right-click the variable whose value you want to change and select Change Value.

How do you change a variable value in Python?

The simplest way to swap the values of two variables is using a temp variable. The temp variables is used to store the value of the fist variable ( temp = a ). This allows you to swap the value of the two variables ( a = b ) and then assign the value of temp to the second variable.

Can I reassign a variable in Python?

Reassigning variablesWe can reassign variable values in Python as follows. The reassignment works similarly to how we create the variable. We only provide a different value than what was stored before. Here, the initial value of a was 10 , which was reassigned to 20 from the second line in the above code block.


2 Answers

I know this question is kind of old, but as I came around the same problem, here is my solution:

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

It's not pretty as it makes heavy use of global variables, but it seems to work.

For example:

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

print(x)

Result:

5
7
5

or with functions:

def func1():
    print("hi")

def func2():
    print("bye")

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

func1()

Result:

hi
bye
hi
like image 194
arthaigo Avatar answered Sep 18 '22 12:09

arthaigo


I normally use this custom attr_as context manager:

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)

You can then use it with the exact same set of arguments you'd use for setattr in 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) 

Note, if you need to preserve the existence/nonexistence of the attribute rather than just the value, that's also possible with just a few more lines:

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)
like image 30
AJMansfield Avatar answered Sep 21 '22 12:09

AJMansfield