如何使用`with`语句检查对象是否已创建?

vpa*_*pas 8 python with-statement

我想确保该类仅在"with"语句中实例化.

即这个是好的:

with X() as x:
 ...
Run Code Online (Sandbox Code Playgroud)

这不是:

x = X()
Run Code Online (Sandbox Code Playgroud)

我该如何确保这样的功能?

the*_*eye 12

据我所知,没有直接的方法.但是,__enter__在调用对象中的实际方法之前,您可以使用布尔标志来检查是否已调用.

class MyContextManager(object):

    def __init__(self):
        self.__is_context_manager = False

    def __enter__(self):
        print "Entered"
        self.__is_context_manager = True
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print "Exited"

    def do_something(self):
        if not self.__is_context_manager:
            raise Exception("MyContextManager should be used only with `with`")

        print "I don't know what I am doing"
Run Code Online (Sandbox Code Playgroud)

当你使用它时with,

with MyContextManager() as y:
    y.do_something()
Run Code Online (Sandbox Code Playgroud)

你会得到

Entered
I don't know what I am doing
Exited
Run Code Online (Sandbox Code Playgroud)

但是,当您手动创建对象并调用时do_something,

x = MyContextManager()
x.do_something()
Run Code Online (Sandbox Code Playgroud)

你会得到

Traceback (most recent call last):
  File "/home/thefourtheye/Desktop/Test.py", line 22, in <module>
    x.do_something()
  File "/home/thefourtheye/Desktop/Test.py", line 16, in do_something
    raise Exception("MyContextManager should be used only with `with`")
Exception: MyContextManager should be used only with `with`
Run Code Online (Sandbox Code Playgroud)

注意:这不是一个可靠的解决方案.有人可以__enter__在调用任何其他方法之前直接单独调用方法,并且__exit__在这种情况下可能永远不会调用该方法.

如果你不想在每个函数中重复检查,你可以使它成为装饰器,就像这样

class MyContextManager(object):

    def __init__(self):
        self.__is_context_manager = False

    def __enter__(self):
        print "Entered"
        self.__is_context_manager = True
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print "Exited"

    def ensure_context_manager(func):
        def inner_function(self, *args, **kwargs):
            if not self.__is_context_manager:
                raise Exception("This object should be used only with `with`")

            return func(self, *args, **kwargs)
        return inner_function

    @ensure_context_manager
    def do_something(self):
        print "I don't know what I am doing"
Run Code Online (Sandbox Code Playgroud)

  • 为什么要经历所有状态切换的麻烦呢?更好的方法是不从“__enter__”返回“self”。返回一个*不同的*对象,并让该对象实现 `do_something()`。所以 `MyContextManager()` 根本没有 `do_something()` 方法**。从“__enter__”返回的对象确实如此。 (3认同)
  • 您可以使用“__getattribute__”来真正确定所有内容 (2认同)
  • 我不会说这不是一个可靠的解决方案。任何其他事情都是过大的/有害的,因为有时可以直接调用enter / exit非常重要(例如contextlib.ExitStack)。 (2认同)

Ant*_*ala 8

没有万无一失的方法来确保在with子句中构造实例,但是您可以在__enter__方法中创建一个实例并返回该实例而不是self; 这是将分配给的值x.因此,您可以将其X视为在其__enter__方法中创建实际实例的工厂,例如:

class ActualInstanceClass(object):
    def __init__(self, x):
        self.x = x

    def destroy(self):
        print("destroyed")

class X(object):
    instance = None
    def __enter__(self):

        # additionally one can here ensure that the
        # __enter__ is not re-entered,
        # if self.instance is not None:
        #     raise Exception("Cannot reenter context manager")
        self.instance = ActualInstanceClass(self)

    def __exit__(self, exc_type, exc_value, traceback):
        self.instance.destroy()
        return None

with X() as x:
    # x is now an instance of the ActualInstanceClass
Run Code Online (Sandbox Code Playgroud)

当然这仍然是可重用的,但每个with语句都会创建一个新实例.

当然,人们可以__enter__手动调用,或者获取ActualInstanceClass对它的引用,但更多的是滥用而不是使用.


对于一个更有气味的方法,X()调用时实际上创建了一个XFactory实例,而不是X实例; 而这当用作上下文管理器时,会创建ActualX作为子类的实例X,因此isinstance(x, X)将返回true.

class XFactory(object):
    managed = None
    def __enter__(self):
        if self.managed:
            raise Exception("Factory reuse not allowed")

        self.managed = ActualX()
        return self.managed

    def __exit__(self, *exc_info):
        self.managed.destroy()
        return


class X(object):
    def __new__(cls):
        if cls == X:
            return XFactory()
        return super(X, cls).__new__(cls)

    def do_foo(self):
        print("foo")

    def destroy(self):
        print("destroyed")

class ActualX(X):
    pass

with X() as x:
    print(isinstance(x, X))  # yes it is an X instance
    x.do_foo()               # it can do foo

# x is destroyed

newx = X()
newx.do_foo()  # but this can't,
# AttributeError: 'XFactory' object has no attribute 'do_foo'
Run Code Online (Sandbox Code Playgroud)

您可以更进一步,并XFactory创建一个X带有特殊关键字参数的实际实例__new__,但我认为它太黑魔法才有用.


lai*_*e9m 5

所有的答案至今没有提供什么样的(我认为)OP想直接
(我认为)OP 想要这样的东西:

>>> with X() as x:
 ...  # ok

>>> x = X()  # ERROR

Traceback (most recent call last):
  File "run.py", line 18, in <module>
    x = X()
  File "run.py", line 9, in __init__
    raise Exception("Should only be used with `with`")
Exception: Should only be used with `with`
Run Code Online (Sandbox Code Playgroud)

这就是我想出的,它可能不是很健壮,但我认为它最接近 OP 的意图。

import inspect
import linecache

class X():

    def __init__(self):
        if not linecache.getline(__file__,
            inspect.getlineno(inspect.currentframe().f_back)
        ).startswith("with "):
            raise Exception("Should only be used with `with`")

    def __enter__(self):
        return self

    def __exit__(self, *exc_info):
        pass
Run Code Online (Sandbox Code Playgroud)

只要withX()使用上下文管理器时在同一行中,这将提供与我上面显示的完全相同的输出。

  • 这就是我们不能拥有美好事物的原因。 (17认同)
  • 我知道你不喜欢它,我也不喜欢这个解决方案,但我仍然把它贴在这里,因为这就是 OP 想要的。 (2认同)