Question: I have a validator class with a method that performs multiple checks and may raise different exceptions: class Validator: def validate(something) -> None: if a: raise ErrorA() if b: raise ErrorB() if c: raise ErrorC()  There's a place in the outside (caller) code where I want to customize its behaviour and prevent ErrorB from being raised, without preventing ErrorC. Something like resumption semantics would be useful here. Hovewer, I haven't found a good way to achieve this. To clarify: I have the control over Validator source code, but prefer to preserve its existing interface as much as possible. Some possible solutions that I've considered:  The obvious try: validator.validate(something) except ErrorB: ...  is no good because it also suppresses ErrorC in cases where both ErrorB and ErrorC should be raised.  Copy-paste the method and remove the check: # In the caller module class CustomValidator(Validator): def validate(something) -> None: if a: raise ErrorA() if c: raise ErrorC()  Duplicating the logic for a and c is a bad idea and will lead to bugs if Validator changes.  Split the method into separate checks: class Validator: def validate(something) -> None: self.validate_a(something) self.validate_b(something) self.validate_c(something) def validate_a(something) -> None: if a: raise ErrorA() def validate_b(something) -> None: if b: raise ErrorB() def validate_c(something) -> None: if c: raise ErrorC() # In the caller module class CustomValidator(Validator): def validate(something) -> None: super().validate_a(something) super().validate_c(something)  This is just a slightly better copy-paste. If some validate_d() is added later, we have a bug in CustomValidator.  Add some suppression logic by hand: class Validator: def validate(something, *, suppress: list[Type[Exception]] = []) -> None: if a: self._raise(ErrorA(), suppress) if b: self._raise(ErrorB(), suppress) if c: self._raise(ErrorC(), suppress) def _raise(self, e: Exception, suppress: list[Type[Exception]]) -> None: with contextlib.suppress(*suppress): raise e  This is what I'm leaning towards at the moment. There's a new optional parameter and the raise syntax becomes kinda ugly, but this is an acceptable cost.  Add flags that disable some checks: class Validator: def validate(something, *, check_a: bool = True, check_b: bool = True, check_c: bool = True) -> None: if check_a and a: raise ErrorA() if check_b and b: raise ErrorB() if check_c and c: raise ErrorC()  This is good, because it allows to granually control different checks even if they raise the same exception. However, it feels verbose and will require additional maintainance as Validator changes. I actually have more than three checks there.  Yield exceptions by value: class Validator: def validate(something) -> Iterator[Exception]: if a: yield ErrorA() if b: yield ErrorB() if c: yield ErrorC()  This is bad, because it's a breaking change for existing callers and it makes propagating the exception (the typical use) way more verbose: # Instead of # validator.validate(something) e = next(validator.validate(something), None) if e is not None: raise e  Even if we keep everything backwards-compatible class Validator: def validate(something) -> None: e = next(self.iter_errors(something), None) if e is not None: raise e def iter_errors(something) -> Iterator[Exception]: if a: yield ErrorA() if b: yield ErrorB() if c: yield ErrorC()  The new suppressing caller still needs to write all this code: exceptions = validator.iter_errors(something) e = next(exceptions, None) if isinstance(e, ErrorB): # Skip ErrorB, don't raise it. e = next(exceptions, None) if e is not None: raise e  Compared to the previous two options: validator.validate(something, suppress=[ErrorB])  validator.validate(something, check_b=False)   

Answer: With bare exceptions you are looking at the wrong tool for the job. In Python, to raise an exception means that execution hits an exceptional case in which resuming is not possible. Terminating the broken execution is an express purpose of exceptions.  Execution Model: 4.3. Exceptions Python uses the “termination” model of error handling: an exception handler can find out what happened and continue execution at an outer level, but it cannot repair the cause of the error and retry the failing operation (except by re-entering the offending piece of code from the top).  To get resumption semantics for exception handling, you can look at the generic tools for either resumption or for handling.  Resumption: Coroutines Python's resumption model are coroutines: yield coroutine-generators or async coroutines both allow to pause and explicitly resume execution. def validate(something) -> Iterator[Exception]: if a: yield ErrorA() if b: yield ErrorB() if c: yield ErrorC()  It is important to distinguish between send-style "proper" coroutines and iterator-style "generator" coroutines. As long as no value must be sent into the coroutine, it is functionally equivalent to an iterator. Python has good inbuilt support for working with iterators: for e in validator.iter_errors(something): if isinstance(e, ErrorB): continue # continue even if ErrorB happens raise e  Similarly, one could filter the iterator or use comprehensions. Iterators easily compose and gracefully terminate, making them suitable for iterating exception cases.  Effect Handling Exception handling is just the common use case for the more generic effect handling. While Python has no builtin effect handling support, simple handlers that address only the origin or sink of an effect can be modelled just as functions: def default_handler(failure: BaseException): raise failure def validate(something, failure_handler = default_handler) -> None: if a: failure_handler(ErrorA()) if b: failure_handler(ErrorB()) if c: failure_handler(ErrorC())  This allows the caller to change the effect handling by supplying a different handler. def ignore_b_handler(failure: BaseException): if not isinstance(failure, ErrorB): raise failure validate(..., ignore_b_handler)  This might seem familiar to dependency inversion and is in fact related to it. There are various stages of buying into effect handling, and it is possible to reproduce much if not all features via classes. Aside from technical functionality, one can implement ambient effect handlers (similar to how try "connects" to raise automatically) via thread local or context-local variables.