10

Python 3.4 提供了这个简洁的工具来临时重定向标准输出:

# 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来自aresys.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

我可以通过扎根 in 来完成这项工作globals(),但没有人会选择使用它。

更新:虽然我认为我的“foo = 10”示例阐明了我想要做的事情,但它们并没有传达实际的用例。这里有两个:

  1. 重定向stderr,很像redirect_stdout
  2. 临时更改 sys.excepthook。我以交互方式进行了大量开发,当我向异常钩子添加一些东西时(通过将原始函数包装在我自己的一个函数中,例如,使用日志记录模块记录异常),我通常希望它在某个时候被删除。这样我就不会有越来越多的包装自己的函数副本。这个问题面临一个密切相关的问题。
4

5 回答 5

9

我知道这个问题有点老了,但是当我遇到同样的问题时,这是我的解决方案:

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
于 2015-09-24T13:59:40.733 回答
3

基于@arthaigo 的答案,更简洁的版本是:

import contextlib

@contextlib.contextmanager
def temporary_assignment(object, new_value):
  old_value = eval(object)
  globals()[object] = new_value
  yield
  globals()[object] = old_value
于 2019-07-26T20:37:58.793 回答
2

我通常使用这个自定义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然后,您可以将它与您在 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) 

请注意,如果您需要保留属性的存在/不存在而不仅仅是值,那么只需多几行也可以:

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)
于 2021-03-02T19:30:21.560 回答
2

我刚刚找到了另一种聪明的方法来做到这一点,使用unittest.mock, 记录在这里

这很笼统,因为可以指定一个包含许多变量的字典:

import unittest.mock
a = b = 1
with unittest.mock.patch.dict(locals(), a=2, b=3):
  print(a, b)  # shows 2, 3
print(a, b)  # shows 1, 1

此外,即使变量之前没有在当前范围内定义,它也可以工作。

with unittest.mock.patch.dict(locals(), c=4):
  assert 'c' in locals()
assert 'c' not in locals()
于 2020-12-16T17:44:54.230 回答
1

怎么样closure

IE

#!/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

而且由于这是一个闭包,它可以被大大扩展以保留一大堆其他变量和状态,然后要么纯粹用于未来的闭包形式,要么用作稍后恢复状态的板岩完毕。

于 2014-05-11T21:06:23.110 回答