问题
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:
- Redirect stderr, much like redirect_stdout
- 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.
回答1:
Building on the answer by @arthaigo, a more concise version is:
from contextlib import contextmanager
@contextmanager
def temporary_assignment(object, new_value):
old_value = eval(object)
globals()[object] = new_value
yield
globals()[object] = old_value
回答2:
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
回答3:
How about a 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)
This yields:
x: 21
x2: 10
And since this is a closure, it could be substantially expanded to preserve a whole bunch of other variables and state, and then either used purely in the closure form going forward, or used as the slate from which to restore state later once you're done.
来源:https://stackoverflow.com/questions/23597692/temporarily-changing-a-variables-value-in-python