能否创建一个上下文相关的Python上下文管理器,用于保存、修改和恢复状态?

4

我有一对Python函数,目前能够在两个值之间切换一个全局变量。我希望将它们转换为上下文管理器,这样我就可以将它们用作with块,在块内设置变量,但在块后还原它。以下是期望的行为:

>>> MODE
'user'
>>> mode_sudo()  # Sets MODE to 'sudo'...
>>> MODE
'sudo'
>>> mode_user()  # Sets MODE to 'user'...
>>> MODE
'user'
>>> with mode_sudo():
...    print MODE
'sudo'
>>> MODE
'user'

这样的妄想可能吗?

更新: 仅为了清晰起见,这里是仅使用上下文管理器的实现:

from contextlib import contextmanager

@contextmanager
def mode_sudo():
    global MODE
    old_mode = MODE
    MODE = 'sudo'
    yield
    MODE = old_mode

@contextmanager
def mode_user():
    global MODE
    old_mode = MODE
    MODE = 'user'
    yield
    MODE = old_mode

如果不使用with关键字调用它们,会返回一个生成器。是否有办法在纯函数调用和巧克力上下文管理器中获取模式翻转的行为?


测试MODE的值有何意义? - Evpok
没什么,我想。在仔细审查后,边缘情况消失了。 :P - David Eyk
我已经删除了无意义的值测试。 - David Eyk
1
def set_mode(mode): 和之后的 MODE = mode 怎么样?这样你只需要写一次代码... - glglgl
在某些情况下,这可能是更好的设计,但在这种情况下,我碰巧正在尝试以向后兼容的方式将上下文管理器纳入现有的代码库中。 - David Eyk
2个回答

8

就像这样做:

class mod_user:

    def __init__(self):
        global MODE
        self._old_mode = MODE
        MODE = "user"

    def __enter__(self):
        pass

    def __exit__(self, *args, **kws):
        global MODE
        MODE = self._old_mode

MODE = "sudo"

with mod_user():
    print MODE  # print : user.

print MODE  # print: sudo.

mod_user()
print MODE   # print: user.

正是我所需要的。而且不依赖于contextlib,这很好。 :) - David Eyk
1
狡猾但不失优雅,我从未想过使用构造函数来伪造可调用对象。 - Evpok
2
可能并不是一件经常做的好事,但在这种情况下,效果绝对是值得的。 - David Eyk
1
关于将上下文管理器类命名为mod_user而不是ModUser,似乎并没有强制执行上下文管理器类的命名惯例。但是,更符合Python风格的写法应该是:with ModUser() as mod_user: ... - smci

2

简单方法:

from contextlib import contextmanager
@contextmanager
def mode_user():
    global MODE
    old_mode = MODE
    MODE = "user"
    yield
    MODE = old_mode

同样适用于mode_sudo()。有关详细信息,请参见文档。这实际上是整个“定义实现__enter____exit__”的类的快捷方式。


糟糕,我刚刚发布了一个编辑,看起来几乎和你的代码一模一样。:) 但这只回答了问题的一半。如果你只是调用这个实现而没有使用with语句,你会得到一个生成器。 - David Eyk
重点是当您执行 with mode_user() 时,它会调用 mode_user__call __() 方法,就像在 with 子句之外简单调用 mode_user() 一样。我无法想到一种方法来区分您是否在 with 子句中。 - Evpok
我们两个都被contextlib及其生成器快捷方式所限制。@mouad的纯协议实现解决了这个问题:__exit__除了在with语句中不会被调用。 - David Eyk
@David 我并没有看到这样的情况 - 如果我尝试上面提到的代码并调用mode_user(),我得到的是一个<contextlib.GeneratorContextManager object>,它不是一个生成器,甚至不可迭代。如果你不喜欢这种行为,当然可以像上面那样自己编写一个类。 - glglgl
1
@glglgl 嗯,看起来你是对的——它不可迭代。然而,我反对的主要观点仍然存在:在调用 __enter__ 之前什么也不会发生,因此对于这种特定的用例并没有帮助。 - David Eyk
啊,好的。现在再仔细看一眼,我看到了区别:模式切换发生在__init__()中,而不是在这里的__enter__()中。那么就清楚了。谢谢你指出来! - glglgl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接