How to check if an object is created with `with` statement?

There is no straight forward way, as far as I know. But, you can have a boolean flag, to check if __enter__ was invoked, before the actual methods in the objects were called.

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"

When you use it with with,

with MyContextManager() as y:
    y.do_something()

you will get

Entered
I don't know what I am doing
Exited

But, when you manually create an object, and invoke do_something,

x = MyContextManager()
x.do_something()

you will get

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`

Note: This is not a solid solution. Somebody can directly invoke __enter__ method alone, before calling any other methods and the __exit__ method may never be called in that case.

If you don't want to repeat that check in every function, you can make it a decorator, like this

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"

There is no foolproof approach to ensure that an instance is constructed within a with clause, but you can create an instance in the __enter__ method and return that instead of self; this is the value that will be assigned into x. Thus you can consider X as a factory that creates the actual instance in its __enter__ method, something like:

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)
        return self.instance

    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

Of course this is still reusable, but every with statement would create a new instance.

Naturally one can call the __enter__ manually, or get a reference to the ActualInstanceClass but it would be more of abuse instead of use.


For an even smellier approach, the X() when called does actually create a XFactory instance, instead of an X instance; and this in turn when used as a context manager, creates the ActualX instance which is the subclass of X, thus isinstance(x, X) will return 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'

You could take this further and have XFactory create an actual X instance with a special keyword argument to __new__, but I consider it to be too black magic to be useful.