用于临时变量赋值的Python上下文管理器

问题描述 投票:0回答:4

我经常需要暂时用其他东西交换变量的值,进行一些依赖于该变量的计算,然后将变量恢复为其原始值。例如:

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
功能。实际上,这甚至不是一个函数,而是一串代码,它作为某些元编程的一部分在全局命名空间的上下文中进行计算。我给出的例子是我能想到的最简单的例子,它解决了我对临时变量的问题。我没有要求获得示例代码的解决方法(正确的版本),而是要求获得我书面问题的答案。

python python-3.x with-statement contextmanager
4个回答
6
投票

不可以,因为上下文管理器不能像这样在调用者的作用域中分配变量。 (任何认为你可以使用

locals
inspect
来做到这一点的人,请尝试使用你在函数内部提出的上下文管理器。它不会工作。)

实用程序可用于处理非局部变量的内容,例如模块全局变量、其他对象属性和字典...但它们是

unittest.mock.patch
及其相关函数,因此您应该强烈在非测试环境中使用它们之前考虑其他替代方案。像“暂时修改这个东西然后恢复它”这样的操作往往会导致代码混乱,并且可能表明您使用了太多的全局状态。


3
投票

简单回答您的问题:

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

是“不,不是。”


2
投票

我的错误,也许是这样的,它不是内置的:

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() 作为对局部变量的引用,并且它可能会产生意想不到的后果,但无论如何仍然可能。

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


0
投票

一般来说,这就是堆栈的用途。在 python 中,变量作用域仅由函数和类定义创建。块不会创建作用域,因此为此使用块级构造有点不传统。

您可以编写一个类或函数来给自己一个临时作用域。

var = 0
def temporary_variable_change():
    var = 1
    do_something_with_var()

也就是说,听起来有人在滥用单例(反)模式。这里的问题是 do_something_with_var 太有状态了。如果它可以分解为无状态函数和有状态的东西,比如类/对象,那么它会更容易理解(以及测试和维护)。

singleton_var = 0
def do_something_with_var()
    global singleton_var
    singleton_var = do_something(singleton_var)

def do_something(var):
    return var + 1.  # or whatever you're actually doing

if __name__ == '__main__':
    do_something(1)  # no need to modify anything

或者更好

class SystemStatefulOperator:
    def __init__(self):
        self.var = 0

    def do_something(self):
        self.var = self.do_something_stateless(self.var)

    @classmethod
    def do_something_stateless(cls, var):
        return var + 1. # again, whatever it is that you're actually doing

if __name__ == '__main__':
    singleton_thing = SystemStatefulOperator()
    # presumably you do something with this

    do_something_stateless(1)  # no need to modify global state 

我知道 OP 要求特定问题的特定答案,但 SO 也适用于所有技能水平的开发人员,包括那些可能不理解惯用模式的开发人员。这种类型的全局状态修改是一种明确的反模式。每个函数必须了解的其他函数的状态越多,代码就越容易出错且难以维护。不可避免地,每个应用程序都有一些程序生命周期状态,但是您越接近显式实例化该状态,您的代码就越容易理解

© www.soinside.com 2019 - 2024. All rights reserved.