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

前端 未结 6 1093
太阳男子
太阳男子 2021-01-02 02:15

I would like to ensure that the class is only instantiated within a \"with\" statement.

i.e. this one is ok:

with X() as x:
 ...

an

6条回答
  •  孤城傲影
    2021-01-02 02:32

    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)
    
        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.

提交回复
热议问题