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)