编写一个只能用作上下文管理器的Python类

时间:2015-06-05 18:27:38

标签: python contextmanager

在Python中是否有办法编写一个会出错的类,除非它与with语句一起使用?

# Okay:
with Foo() as f1:
    f1.func1()
    f1.func2()

# Not okay:
f2 = Foo()
f2.func1()

我可以手动执行:让__enter__设置一个标志,并让其他所有方法检查该标志。但是有更好的方法吗?

这里的代码是不那么自然的方式:

class Foo(object):
    def __init__(self):
        self._entered = False

    def __enter__(self):
        self._entered = True
        return self

    def _verify_entered(self):
        if not self._entered:
            raise Exception("Didn't get call to __enter__")

    def __exit__(self, typ, val, traceback):
        self._verify_entered()
        print("In __exit__")

    def func1(self):
        self._verify_entered()
        # do stuff ...

    def func2(self):
        self._verify_entered()
        # do other stuff

2 个答案:

答案 0 :(得分:3)

从技术上讲,我认为agf是正确的,因为你可以使用元类来自动化这些东西。但是,如果我理解其背后的根本动机,我建议采用不同的方式。

假设您要通过上下文管理器保护Payload类。在这种情况下,您只需创建一个返回它的上下文管理器:

# This should go in a private module.
class Payload(object):
    def __init__(self):
        print 'payload ctor'

# This should go in the public interface.
class Context(object):
    def __init__(self):
        # Set up here the parameters.
        pass

    def __enter__(self):
        # Build & return a Payload object
        return Payload()

    def __exit__(self, exc_type, exc_val, exc_tb):
        # Cleanup
        pass

with Context() as f:
    # f here is a Payload object.

如果您将Payload隐藏在私人模块中,那么您就可以了。

答案 1 :(得分:2)

如果您不希望用户能够在上下文管理器对象本身上调用方法,则可以让__enter__方法返回与self不同的对象。

class Foo(object):
    def __enter__(self):
        print("In __enter__")
        return Bar()

    def __exit__(self, typ, val, traceback):
        print("In __exit__")

class Bar(object):
    def func1(self):
        print("In func1")

    def func2(self):
        print("In func2")

当然,您可以将FooBar类绑定在一起,而不是本示例。例如,Foo类可以将自己传递给Bar中的__enter__构造函数。

由于Foo().func1()没有任何此类方法的显而易见的原因,调用Foo将无效。如果您希望用户看不到Bar,则可以在其名称前加上下划线(暗示它是内部的),或者甚至将其嵌套在Foo类中(甚至__enter__方法,如果你真的想要极端)。