【问题标题】:Is it possible to make a contextually-sensitive python context manager that saves, modifies and restores state?是否可以制作一个上下文敏感的 python 上下文管理器来保存、修改和恢复状态?
【发布时间】:2011-10-22 17:52:47
【问题描述】:

我有一对 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

调用这些不带关键字返回一个生成器。有没有办法通过普通函数调用和巧克力上下文管理器来获得模式翻转行为?

【问题讨论】:

  • 测试MODE的值有什么意义?
  • 不多,我想。仔细检查时,边缘案例会蒸发。 :P
  • 我已经删除了愚蠢的价值测试。
  • def set_mode(mode): 和之后的MODE = mode 怎么样?所以你只需要拥有一次代码......
  • 在某些情况下这可能是一个更好的设计,但在这种情况下,我碰巧试图以向后兼容的方式将上下文管理器用于现有代码库。

标签: python state contextmanager stateful


【解决方案1】:

这样做:

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:很高兴它有帮助:-)
  • 狡猾但很好,我从没想过使用构造函数来伪造可调用对象。
  • 可能一直都不是什么好事,但在这种情况下,效果绝对值得。
  • 关于将上下文管理器类命名为mod_user 而不是ModUser,似乎没有强制执行的naming convention for context-manager classes。但写起来似乎更像 Pythonic:with ModUser() as mod_user: ...
【解决方案2】:

简单的方法:

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

同上mode_sudo()。有关详细信息,请参阅the doc。它实际上是整个“定义一个实现__enter____exit__的类”的捷径。

【讨论】:

  • 糟糕,我刚刚发布了一个与您的代码几乎一模一样的编辑。 :) 但这只回答了一半的问题。如果你只调用这个实现而不带 with 语句,你会得到一个生成器。
  • 重点是当您执行with mode_user() 时,它会调用mode_user__call__() 方法,就像您在with 子句之外仅调用mode_user() 一样。我想不出一种方法来区分您是否在with 子句中。
  • 我们都被contextlib 和它的生成器快捷方式包围了。 @mouad 的纯协议实现解决了这个问题:__exit__ 永远不会被调用,除非在 with 语句中。
  • @David 我不这么认为 - 如果我尝试上面的代码并调用 mode_user(),我会得到一个不是生成器的 ,它甚至是不可迭代的.如果你不喜欢这种行为,你当然可以像上面那样制作你自己的类。
  • @glglgl 好吧,看起来你是对的——它不可迭代。但是,我反对的主要观点仍然存在:在调用 __enter__ 之前什么都不会发生,这对于这个特定的用例没有帮助。
猜你喜欢
  • 2012-11-01
  • 1970-01-01
  • 2020-10-21
  • 1970-01-01
  • 1970-01-01
  • 2020-09-25
  • 2011-08-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多