12

I'm often in the need of temporarily switching the value of a variable out with something else, do some computation which depends on this variable, and then restore the variable to its original value. E.g:

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

This seems like an obvious opportunity for using a context manager (the with statement). Does the Python standard library contain any such context manager?

Edit

I am aware that this sort of thing is most often handled by other, better means than temporarily changing a variable. I am however not asking for obvious workarounds.

In my actual work case, I can not alter the do_something_with_var function. Actually this is not even a function, but a string of code which gets evaluated in the context of the global namespace as part of some metaprogramming. The example I gave was the simplest I could think of that kept my problem with the temporary variable. I did not ask to get a workaround (proper version) of my example code, but rather to get an answer on my written question.

jmd_dk
  • 12,125
  • 9
  • 63
  • 94
  • 1
    I chose not to vote on the question, but I'd argue that if you often need such a construct there are bigger design issues to be addressed. – NPE Jan 04 '17 at 01:05
  • Previously answered: http://stackoverflow.com/questions/3024925/python-create-a-with-block-on-several-context-managers?rq=1 – Bill Schumacher Jan 04 '17 at 01:05
  • 4
    @BillSchumacher: Uh, what? How is that question relevant? – user2357112 Jan 04 '17 at 01:06
  • 2
    how is this off-topic? anyone using jupyter runs into this situation all the time. i'd love to have a context manager like `with Namespace() as ns:` to have a little closure or whatever it is, and then return to the parent namespace after execution. (i'm not a computer scientist - someone correct me if i'm not using these words correctly) – william_grisaitis Oct 16 '19 at 22:57
  • 2
    This is an excellent question. Why has it been closed?? It says "needs debugging details" but with the edit it is completely clear (to me at least) what is the desired behavior. Absurd! Please either keep it open or provide some coherent justification. – Ben Mares May 30 '20 at 10:07

3 Answers3

6

Nope, because a context manager can't assign variables in the caller's scope like that. (Anyone who thinks you could do it with locals or inspect, try using the context manager you come up with inside a function. It won't work.)

There are utilities for doing that with things that aren't local variables, such as module globals, other object attributes, and dicts... but they're unittest.mock.patch and its related functions, so you should strongly consider other alternatives before using them in a non-testing context. Operations like "temporarily modify this thing and then restore it" tend to lead to confusing code, and may indicate you're using too much global state.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • I did think that something like `inspect` would be needed if I were to write the context manager myself, which is too ugly for me to bother. But why should this not even be possible? Does `with` somehow hide the function stacks? – jmd_dk Jan 04 '17 at 01:15
  • @jmd_dk: No, it's just that Python doesn't provide access to the data structures that hold function-local variables. – user2357112 Jan 04 '17 at 01:17
  • @jmd_dk - It's not possible because it's not remotely necessary. Functions work with global values in a way that doesn't affect those values by accepting them as parameters (and making a copy within the function, if necessary). – TigerhawkT3 Jan 04 '17 at 01:27
3

The simple answer to your question:

Does the Python standard library contain any such context manager?

is "No, it does not."

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • 2
    I'm pretty sure we all know he wants a work-able solution as well ;-) – Christian Dean Jan 04 '17 at 01:17
  • @leaf: I think it's fair to say that we also know that the question is pretty misguided, so giving workable solutions isn't necessarily constructive. ;-) – NPE Jan 04 '17 at 01:19
  • 8
    @NPE I'm so tired of that attitude. Half of the question (the edit) is taken up by a pointless confirmation that I really do want an actual answer, rather than being dismissed. I had a technical question and expected a technical answer. – jmd_dk Feb 13 '19 at 15:39
2

My mistake, instead perhaps something like this, it is not built-in:

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

Output:

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

For clarity, so you know it's actually doing something:

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

Output:

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

You could also do this:

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

It should be noted that if you try to use the with statement within a function, you'll want to use globals() as the reference to locals and it may have unintended consequences, might still anyways.

I wouldn't recommend doing this at all, but should work.

Bill Schumacher
  • 231
  • 2
  • 6
  • Thanks, but it appears that you still manually set `a = 5` back again once the work is done. Also, `do_some_work` should only read the global `a`, not reassign it. – jmd_dk Jan 04 '17 at 13:10
  • The bottom example was meant to show normal python operation without resetting the variables at all. – Bill Schumacher Jan 04 '17 at 15:56