Skip to content
Advertisement

Python How to force object instantiation via Context Manager?

I want to force object instantiation via class context manager. So make it impossible to instantiate directly.

I implemented this solution, but technically user can still instantiate object.

class HessioFile:
    """
    Represents a pyhessio file instance
    """
    def __init__(self, filename=None, from_context_manager=False):
        if not from_context_manager:
            raise HessioError('HessioFile can be only use with context manager')

And context manager:

@contextmanager
def open(filename):
    """
    ...
    """
    hessfile = HessioFile(filename, from_context_manager=True)

Any better solution ?

Advertisement

Answer

If you consider that your clients will follow basic python coding principles then you can guarantee that no method from your class will be called if you are not within the context.

Your client is not supposed to call __enter__ explicitly, therefore if __enter__ has been called you know your client used a with statement and is therefore inside context (__exit__ will be called).

You just need to have a boolean variable that helps you remember if you are inside or outside context.

class Obj:
    def __init__(self):
        self._inside_context = False

    def __enter__(self):
        self._inside_context = True
        print("Entering context.")
        return self

    def __exit__(self, *exc):
        print("Exiting context.")
        self._inside_context = False

    def some_stuff(self, name):
        if not self._inside_context:
            raise Exception("This method should be called from inside context.")
        print("Doing some stuff with", name)

    def some_other_stuff(self, name):
        if not self._inside_context:
            raise Exception("This method should be called from inside context.")
        print("Doing some other stuff with", name)


with Obj() as inst_a:
    inst_a.some_stuff("A")
    inst_a.some_other_stuff("A")

inst_b = Obj()
with inst_b:
    inst_b.some_stuff("B")
    inst_b.some_other_stuff("B")

inst_c = Obj()
try:
    inst_c.some_stuff("c")
except Exception:
    print("Instance C couldn't do stuff.")
try:
    inst_c.some_other_stuff("c")
except Exception:
    print("Instance C couldn't do some other stuff.")

This will print:

Entering context.
Doing some stuff with A
Doing some other stuff with A
Exiting context.
Entering context.
Doing some stuff with B
Doing some other stuff with B
Exiting context.
Instance C couldn't do stuff.
Instance C couldn't do some other stuff.

Since you’ll probably have many methods that you want to “protect” from being called from outside context, then you can write a decorator to avoid repeating the same code to test for your boolean:

def raise_if_outside_context(method):
    def decorator(self, *args, **kwargs):
        if not self._inside_context:
            raise Exception("This method should be called from inside context.")
        return method(self, *args, **kwargs)
    return decorator

Then change your methods to:

@raise_if_outside_context
def some_other_stuff(self, name):
    print("Doing some other stuff with", name)
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement