【问题标题】:Temporarily changing a variable's value in Python在 Python 中临时更改变量的值
【发布时间】:2014-05-11 20:41:14
【问题描述】:

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

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

The code 不是很复杂,但我不想一遍又一遍地写它,尤其是考虑到它已经考虑到让它可重入:

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.stderrsys.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。我以交互方式进行了很多开发,当我向异常钩子添加一些东西时(通过将原始函数包装在我自己的一个函数中,例如,使用日志记录模块记录异常),我通常希望它在某个时候被删除。这样我就不会有越来越多的包装自己的函数副本。 This question 面临一个密切相关的问题。

【问题讨论】:

  • 你能激发这样的成语吗?在什么情况下会有用?

标签: python with-statement contextmanager


【解决方案1】:

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

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

【讨论】:

  • 不错。如果您使用exec("{} = {}".format(self.old, repr(self.new)), globals()),我认为您可以获得更通用的东西。有了这个改变,我也可以处理字符串了。
  • 不错。这是一个有用的补充。我只用函数和整数对其进行了测试。
  • 通过完全摆脱 exec ,它现在更加通用。现在,可以传递任意辅助对象。
【解决方案2】:

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

import contextlib

@contextlib.contextmanager
def temporary_assignment(object, new_value):
  old_value = eval(object)
  globals()[object] = new_value
  yield
  globals()[object] = old_value

【讨论】:

  • 不幸的是,这些解决方案都不能跨不同的模块工作。所以这个temporary_assignment 不能放在库模块中并在其他地方使用,因为它无法访问调用者模块中的globals()。另见stackoverflow.com/questions/41454678/…
【解决方案3】:

我刚刚找到了另一种巧妙的方法,使用unittest.mock,记录在案的here

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

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

【讨论】:

    【解决方案4】:

    我通常使用这个自定义的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)
    

    然后,您可以将它与在attr_as 中用于setattr 的一组完全相同的参数一起使用。

    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)
    

    【讨论】:

      【解决方案5】:

      closure 怎么样?

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

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

      【讨论】:

      • 有趣。正如@arshajii 建议的那样,闭包实现了您无法使用临时变量完成的功能?换句话说,不是clo = create_closure(x),而是设置tmp = x?
      • 嗯,作为一个闭包,它的优点是可以随处携带:传递到其他函数、作用域等,并且它总是具有该值。如果您将它用于不仅仅是一个简单的 int,它可能会更有用。即存储包含多个不同变量、结构等的状态。您还可以为不同的值集创建多个它们,这在存储不同点的状态和该性质的事物时可能再次派上用场。 (我上面的例子是为了说明一般概念。)
      • 想象一下这个闭包包含,比如说,10 个不同的变量代表一个状态,并假设你希望有 4 个不同的集合代表你的应用程序中的不同点。您可以在任何时候回忆起它们,传递它们,将它们存储在按时间或其他内容键入的字典中,等等。所有这些都比一堆单独声明的局部变量更具表现力和更容易跟踪。在这种用法中,闭包本质上是一种堆栈框架。
      猜你喜欢
      • 1970-01-01
      • 2011-09-30
      • 2021-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-06
      相关资源
      最近更新 更多