On Fri, Apr 9, 2021 at 1:38 AM anthony.flury via Python-ideas email@example.com wrote:
... indented twice just to accommodate the outer try block.
Treating the 'with' as an implied `try` would reduce the march to the right - now the key processing of the resource is now indented only one level - and the association of the exception from the `with` block is syntactically clear.
Normally I'd be -1 on proposals whose main benefit is "one fewer indentation level". We don't need complexity that serves no other purpose than that. But the with/try combination is somewhat special - the 'with' block is inherently about exception handling.
But there's this big question:
On Fri, Apr 9, 2021 at 2:24 AM Paul Bryan firstname.lastname@example.org wrote:
Q. Safe to assume this would catch exceptions from both the call to `open` as well as the call to `open.__enter__`?
No, not safe to assume. It's equally reasonable to define it as guarding only the body of the statement, or as guarding the header as well. The semantics have to be locked in one way or the other, and half the time that's going to be incorrect.
IMO this would be a bug magnet on the difference between "exceptions that get handed to __exit__" and "exceptions that get caught by the associated except clause". For instance, what happens in this situation:
with database_transaction(conn) as self.trn: self.trn.execute("insert blah blah blah") else: with database_transaction(conn) as self.trn: self.trn.execute("update set blah blah blah")
(Yes, I know a good few database engines have a robust upsert/merge operation, but bear with me here.) The else clause implies that its body will not be executed if any exception was raised. But which of these exceptions would prevent that else from happening?
* NameError on database_transaction or conn * ConnectionLostError inside database_transaction() * MemoryError in the transaction's __enter__() * AttributeError assigning to self.trn * ResourceError during the __del__ of the previous trn (freebie - that should be ignored regardless) * KeyboardInterrupt after assigning to self.trn but before beginning the block * QueryConflictError inside execute() (another freebie - obviously this one should) * CommitFailedError in __exit__, after no other exception * RollbackFailedError in __exit__, after some other exception
(And yes, RollbackFailed is almost a freebie as well, since I have an "else" and no "except" here, but that'd be different with some actual except clauses, so it's worth clarifying.)
If it's defined as simply nesting the with inside a try, then the answer is "all of the above". But that would be inconsistent with the existing meaning, which wouldn't cover nearly as much.
Similar question: What would be the semantics of this?
with contextlib.suppress(BaseException): a = b / c except BaseException as e: print(e)
What types of exception could be caught and what types couldn't?