【问题标题】:Python context manager for temporary variable assignment用于临时变量分配的 Python 上下文管理器
【发布时间】:2017-01-04 01:01:57
【问题描述】:

我经常需要临时将变量的值换成其他东西,做一些依赖于这个变量的计算,然后将变量恢复到它的原始值。例如:

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

这似乎是使用上下文管理器的明显机会(with 语句)。 Python 标准库是否包含任何此类上下文管理器?

编辑

我知道这类事情通常由其他比临时更改变量更好的方法来处理。但是,我并不是要求明显的解决方法。

在我的实际工作案例中,我无法更改do_something_with_var 函数。实际上,这甚至不是一个函数,而是一串代码,它作为某些元编程的一部分在全局命名空间的上下文中被评估。我给出的示例是我能想到的最简单的示例,它使我的问题与临时变量有关。我并没有要求获得示例代码的解决方法(正确版本),而是要求我的书面问题得到答案。

【问题讨论】:

  • 我选择不对这个问题投票,但我认为如果您经常需要这样的结构,那么需要解决更大的设计问题。
  • @BillSchumacher:呃,什么?这个问题有什么关系?
  • 这怎么跑题了?任何使用 jupyter 的人都会遇到这种情况。我很想有一个像with Namespace() as ns: 这样的上下文管理器来有一个小闭包或其他任何东西,然后在执行后返回到父命​​名空间。 (我不是计算机科学家——如果我没有正确使用这些词,请有人纠正我)
  • 这是一个很好的问题。为什么已经关闭了??它说“需要调试详细信息”,但是通过编辑,(至少对我而言)完全清楚所需的行为是什么。荒诞!请保持开放或提供一些连贯的理由。

标签: python python-3.x with-statement contextmanager


【解决方案1】:

不,因为上下文管理器不能像那样在调用者的范围内分配变量。 (任何认为可以使用 localsinspect 的人,请尝试使用您在函数中提出的上下文管理器。它不会起作用。)

实用程序可以处理非局部变量的事情,例如模块全局变量、其他对象属性和字典...但它们是 unittest.mock.patch 及其相关函数,因此在非测试环境中使用它们之前,您应该强烈考虑其他替代方案。诸如“暂时修改这个东西然后恢复它”之类的操作往往会导致代码混乱,并且可能表明您使用了过多的全局状态。

【讨论】:

  • 我确实认为如果我要自己编写上下文管理器,就需要像inspect 这样的东西,这对我来说太难看了。但是为什么这甚至不可能呢? with 是否以某种方式隐藏了函数堆栈?
  • @jmd_dk:不,只是 Python 不提供对保存函数局部变量的数据结构的访问。
  • @jmd_dk - 这是不可能的,因为它不是远程必要的。函数使用全局值的方式不会影响这些值,方法是将它们作为参数接受(并在函数内复制,如有必要)。
【解决方案2】:

您问题的简单答案:

Python 标准库是否包含任何此类上下文管理器?

是“不,它没有。”

【讨论】:

  • 我很确定我们都知道他也想要一个可行的解决方案;-)
  • @leaf:我认为公平地说,我们也知道这个问题被误导了,所以给出可行的解决方案并不一定是建设性的。 ;-)
  • @NPE 我厌倦了这种态度。一半的问题(编辑)被一个毫无意义的确认占据了,我真的想要一个实际的答案,而不是被解雇。我有一个技术问题,希望得到技术答案。
【解决方案3】:

我的错误,可能是这样的,它不是内置的:

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

输出:

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

为了清楚起见,你知道它实际上在做某事:

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

输出:

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

你也可以这样做:

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

应该注意的是,如果您尝试在函数中使用 with 语句,您会希望使用 globals() 作为对局部变量的引用,这可能会产生意想不到的后果,但无论如何。

我根本不建议这样做,但应该可以。

【讨论】:

  • 谢谢,但您似乎仍然在工作完成后再次手动设置 a = 5。此外,do_some_work 应该只读取全局 a,而不是重新分配它。
  • 下面的例子是为了展示正常的python操作,而不需要重置变量。
最近更新 更多