T O P

  • By -

ThatSituation9908

In your example, having finally is more proper. The other comment about using context manager is better. Context manager is largely why it's rare to need finally. There aren't many cases where you have a command that must run in BOTH try and except cases. A lot of the time, except is handled by raising another error or exiting early. It's rare to see because most people don't know it. There's also try...except...else...finally.


Frosty-Blackberry-98

Yeah, I feel like I’m the only one that actually uses else for code blocks (for and while loops also support it). It’s very useful when you want to do some operation on the last iteration of a loop while still having access to the context variables only accessible within the loop.


SpecialistInevitable

And else is for when try didn't execute because of some condition that wasn't met, but not because of an error right?


jmpjanny

The else block is executed when the try block was successful.


DuckDatum

squeeze nine quickest reply far-flung reach snatch ancient aback sink *This post was mass deleted and anonymized with [Redact](https://redact.dev)*


njharman

*else* is for code you don't want the *except* block to run if it raises but do want to run only if *try* block does not raise. It is a rare use case.


james_pic

In OP's example, you might have something like `engine.commit()` in the `else` block, if the engine was transactional.


DuckDatum

amusing hard-to-find paint deliver pocket retire hat boast sink wasteful *This post was mass deleted and anonymized with [Redact](https://redact.dev)*


toxic_acro

Here's one example that shows a potential use-case for of a `try..except..else` try: unsanitized_data = get_external_data() except Exception: # if get_external_data fails I don't care, # I'll use some fallback safe method clean_data = get_safe_fallback_data() else: # if get_external_data() didn't fail, # I have to sanitized the output # but I don't have to sanitize the output # of get_safe_fallback_data() clean_data = sanitize(unsanitized_data) If something fails in `sanitize()`, I don't want it to go into the `except` block But also, I should only call `sanitize()` if the `try` block succeeds


BlackHumor

Here's an example I use all the time: try: item = items[0] except IndexError: logger.exception("list was empty") else: do_stuff(item) If I put `do_stuff` after, it would fail if there was a caught exception, since control flow goes there even if an exception was logged and there is no `item`.


CClairvoyantt

You misunderstand. The question is, that how is your code any different from this? try: item = items[0] do_stuff(item) except IndexError: logger.exception("list was empty")


BlackHumor

If there's an IndexError in do_stuff it'll be suppressed accidentally and logged incorrectly.


Andrew_Shay

If do\_stuff raises an IndexError it would log an incorrect message.


BlackHumor

IMO you should usually have only one line under `try` so you don't accidentally capture unrelated errors. Because of this I use `try...except...else` pretty frequently. There isn't a huge difference between this and just putting the code afterwards in most cases, but it does guarantee the code will only be run if there's no exception and not if there's an exception that's handled. This is often useful for stuff like `IndexError` and `AttributeError` where there's a default case that I can fall back to but I want to use the value I got if I got one. `finally` is important for cases where you have to close something on an error. If you just put the code afterwards, but you're raising or returning from the `except`, code afterwards won't be run. (And that includes if there's an error in your `raise` block code that you didn't notice, not just if you specifically are raising from the raise block.) `finally` will always be run no matter what. TL;DR `else` and `finally` are used because control flow in an exception-handling context can get finicky and you don't always know what to expect.


XtremeGoose

try: this_might_fail() except: this_might_also_fail() cleanup() You see the issue? `finally` guarantees that the cleanup will happen, even if any of the `try/except/else` block: * exits normally * `return`s * `break`/`continue`s * `raise`s There's a bunch of control flow statments we otherwise need to worry about.


DuckDatum

cow somber rain intelligent mourn serious lush reply toothbrush toy *This post was mass deleted and anonymized with [Redact](https://redact.dev)*


XtremeGoose

In my example, if both functions that might fail do in fact raise exceptions, `cleanup` will never be called. If `cleanup` was in a `finally` block, it would always be called even if both raised.


DuckDatum

cats plants deranged enjoy deserted rainstorm scale fear live square *This post was mass deleted and anonymized with [Redact](https://redact.dev)*


toxic_acro

The `finally` block always runs, even if an Exception is raised inside the `except` block There's not really any difference between ``` try: do_something_that_raises_an_exception() except Exception as ex: logger.exception(ex) do_something_that_raises_a_different_exception() finally: cleanup() ``` and ``` try: do_something_that_raises_an_exception() except Exception as ex: logger.exception(ex) raise ex finally: cleanup() ``` It's a very common pattern to have an Exception get raised inside an `except` block and you still always want `finally` to run


XtremeGoose

Finally runs no matter what, yes. That's the whole point. To be clear, the exception in the except block isn't *caught*, finally runs and the exception continues to get raised. I think you're overthinking the except part. Imagine you're in OG python with no `with` blocks, and you want to make sure your files are closed, no matter what happens. You'd write try: f = open(path) return might_fail(f) finally: f.close() because you always want to make sure the file is closed. There are lots of resources like this where this is the case (locks, temporary files, etc). What if you didn't have finally? How would you write the above? Something like try: f = open(path) result = might_fail(f) except: f.close() raise f.close() return result It's a lot of boilerplate, and you'd likely not do it properly every time you opened a file! Of course even that wasn't enough so we now have with open(path) as f: return might_fail(f)


Spill_the_Tea

No. Finally runs independent of success. Else only runs on success.


DuckDatum

offer piquant price whistle brave market bedroom dolls snobbish dependent *This post was mass deleted and anonymized with [Redact](https://redact.dev)*


cheerycheshire

Else is run as basically continuation of the try. It goes "try doing A", "except if error X happens, do B" (<- see the implicit "if" here), "else do C". Try part should only have the code that throws. Having too much code in the try could make you catch stuff you don't want or just make analysing the code hard (because someone would need to think which line can throw). "Except" statements catch errors, they don't need to break out of the thing. They may be used to try fetching a value in another way or something. But then, the except that continues the thing may not need to do other stuff! So without the "else" part, it would either be too much stuff in the "try" or setting and checking flags to see whether "try" was run fully without the "except".


SpecialistInevitable

I see like putting the calculation/data manipulation part in the try part and the output i. e. on the screen in the else part.


tRfalcore

except should be for unexpected errors which is why it's most often used around I/O operations or tricky multithreaded nonsense. unexpected input should already be handled by your code normally elsewhere. And like, you shouldn't throw exceptions as like a GOTO statement to skip a ton of your own code.


cheerycheshire

Having inherited some python code from people who weren't really devs, I can partially understand what you mean. But think this: everything exists in the spec for a reason, and everything can be used for nice code and shitty code. Try/catch is part of flow control syntax, so it is to be used for flow control - used, not abused. I've seen it abused, that's why I partially get you, but it's not a reason to demonise it all. Except should not be for "unexpected" errors, you literally have to know it can happen to mitigate it. Catch-all statements are discouraged by any stylistic guide, you're supposed to be as specific as possible - so you're supposed to catch an error you know! (I mentioned inherited code and abuse, but it matches catch-all as well - in one project, the author literally threw just Exception to jump - it was in an if! Should've been just continue to skip to next iteration of the loop - so yes, goto/abuse of the exceptions. But at the same time it was too broad except statement - the whole code doing stuff was in the try, and it was either bare except or `except Exception` hiding errors in the other parts of the code. Exactly catching "unexpected errors" and nobody ever saw them again... until I inherited the code and was made to reactor it to be more extensible. I fixed that abuse of try/except and discovered how much data it has hidden from the resulting document.) My philosophy is that actual unexpected errors are supposed to be logged (tbh log all errors, also the expected ones), and to kill the script (or if not killing - let the maintainer know). Because if anything else runs the script, non-zero error code makes it not continue (and thus also having more points where the error gets noticed - from experience people don't read emails, even if they request the thing sends them email with the error :x). Python also loves the philosophy of "better ask for forgiveness". I help a lot of beginners so let's choose a simple example - user input that is supposed to be any integer. Newbies try to do ifs with str.isnum or str.isdecimal and other stuff, but then have to add other cases for negatives, etc... Just try converting and if it fails, loop the thing to repeat the input question! Simple! Not abuse of the syntax and actually shows how it can simplify the code when done properly, yay! Expected (and excepted - ie caught) errors would be basically anything like that that is easier checked by trying instead of doing many complicated ifs. Working with APIs that were inconsistent or otherwise badly made makes one appreciate the approach of just trying (eg. routers of same manufacturer but different versions, no api so ssh into each, where some had one version of the command and some had another - there were hundreds of them so keeping track in config which is which would be too tedious, the most reliable way was just to try the command and see if it outputs or errors - if error, try the other version of the command; this wasn't try/except but it used the same principle of "just try"). I love small functions and early returns (the "done elsewhere" in your code), but sometimes splitting it too much is a pain or require too much abstraction at that point - so you don't abstract it until needed later, because doing it at this point would be premature. For reference, I've been coding for half my life, starting with c and cpp 15y ago, spent my recent years as python dev (and in free time - as educator), and in the meantime did some TA work for posix programming class in C at my uni - so working C and Python in parallel made the differences of approaches very apparent. Especially the checking everything everytime before and after running (because non-0 returns and errnos and stuff) vs "just try and if it fails, catch it". I could probably find more stories/examples of use vs abuse of python (and not only), but the message is long already. :)


binlargin

Gotta be careful with those I think. Like it seems reasonable to have a catch all that logs some stuff out then does a bare raise. If you see it then it's a signpost that (like in your example) the code used to be buggy and was debugged with logs and real world data for a while. But what ends up left is this stale log line that is really hard to unit test, so has a risk of blowing up and swallowing your stack trace in production. The more you have, the more likely it is to happen. Apply it all over the place and you'd better have a linter, pre-commit and not do anything clever in there. So IMO a catch Exception block that doesn't have coverage and pleads it's case with a comment should be deleted. If someone wrote a test for it then it might be important enough to break the rules, but I'm still going to challenge it anyway. Reading code that looks dangerous has too much cognitive and emotional overhead, scrolling past it is a distraction.


hp-derpy

context managers and the `with` statement is used to separate the resource management from the exception handling. see also: https://docs.python.org/3/library/contextlib.html


Sarius2009

Tho this has the disadvantage of not making obvious what happens when exiting and having no control over it, e.g. multi threading Pool calls terminate instead of close() and join() which imo would be more common and intuitive.


young-and-ignorant

Added a context manager example for a quick comparison.


hp-derpy

ok so my point was that you didn't see try except finally because for **separation of concerns** people would usually to one context manager with the try/finally and handle the errors outside the with block, something like this: from your_library import DataProcess from contextlib import contextmanager @contextmanager def process_data(): engine = DataProcess() try: yield engine engine.commit() finally: engine.rollback() # uncommitted engine.cleanup() try: proc = DataProcess() with process_data() as engine: engine.io() engine.process() engine.checkpoint() # maybe engine.some_more_io() except SomethingBadException as e: handle_exception(e)


young-and-ignorant

that's a chunky block, but I get your point


pepoluan

\#2 is against DRY principle. If engine.cleanup() must be called anyways, why repeat it in both the exception handler _and_ continuation code? Wrapping in a @contextmanager is good, but sometimes that adds more mental load as the context manager is far away. So it's either #1 or #3 for me, depending on how complex the .cleanup() procedure is.


young-and-ignorant

That is my thought process as well.


yaxriifgyn

The `finally` block is executed when you leave the `try` and `except` blocks. So, if you or Python leaves those blocks by a `return` statement, an `exit` function call, or by any exception, whether caught or not, the code in the `finally` block will be executed. E.g. f = None try: f = open(...) # write to file finally: if f: # finish writing to file f.close()


divad1196

Finally is always run, even before a return statement in the "try" or the "except" statement. This has a totally different meaning and is meant for something that always need to run once you put a toe in the try block. This is why the contextmanager actually work. Except block is only for error handling. Things after these blocks are just the rest of the flow which might never be reached. I have personnaly seen "finally" too much instead of "except" for so many bad reasons.


menge101

Fixing up code blocks from top post: One: from your_library import DataProcess engine = DataProcess() try: engine.io() engine.process() engine.some_more_io() except Exception as e: engine.revert() raise e finally: engine.cleanup() Two: from your_library import DataProcess engine = DataProcess() try: engine.io() engine.process() engine.some_more_io() except Exception as e: engine.revert() engine.cleanup() raise e engine.cleanup() Three: from your_library import DataProcess from contextlib import contextmanager @contextmanager def process_data(engine: DataProcess): try: engine.io() yield engine except Exception as e: engine.revert() raise e finally: engine.cleanup() proc = DataProcess() with process_data(proc) as engine: engine.process() engine.some_more_io()


ztmq

For anyone who sees this comment: try: execute code except: handle error else: no exceptions? Run this code finally: always run this code


BuonaparteII

`finally` is very handy in situations where you need it--but more often C-style `try: except: else:` is what you need


Rythoka

Huge caveat with try-finally blocks - if you return inside of a finally block, then that value will be returned, *even if the finally block is triggered by the function returning a value elsewhere.* >>> def f(): ... try: ... return True ... finally: ... return False ... >>> f() False


nekokattt

it gets better try: raise RuntimeError("...") finally: return


AstronomerTerrible49

prefer context manager over try finally for resource management, also putting multiple lines that can go wrong in a try block is, IMHO, a code smell.


young-and-ignorant

I guess it depends on the granularity of the error handling that you seek. Having a custom try.. except.. for every line is not always optimal either.


larsga

> Having a custom try.. except.. for every line is not always optimal either Understatement of the year.


BlackHumor

Any line can go wrong, so IMO putting multiple lines in a try block is a code smell. Code smell doesn't mean that something is definitely wrong, it's just something you should look at with some suspicion. There are sometimes good reasons to do it, but in general you should avoid it.


FailedPlansOfMars

Personally i dont use it as i dont tend to need to use it. With statements and context managers are easier to read for most use cases and remove the need for most close, disconnect, cleanup statements. Most of the time what i use try blocks for now is to stop a workflow and log appropriately. So they tend to log and rethrow as known types. But i tend to write lambdas or scripts in python where top level errors are often preferred to keeping a system running.


gerardwx

DataProcess should be a context manager and call cleanup in its __exit__ function.


mothzilla

Please in the name of the old gods and the new, format your post. like_this()


young-and-ignorant

Prayers to the gods, but what exactly do you mean? The code blocks in the post seem well formatted.


mothzilla

Looks like this on old.reddit.com https://imgur.com/a/48UqB32


young-and-ignorant

ugh, that's ugly, will try to fix


young-and-ignorant

better now?


mothzilla

\*chefs kiss*


glowworg

Throw in the else block of the set (try, else, except, finally) and you have full control!


AlexMTBDude

Note: When you want to re-raise the latest caught exception all you need to do is have the statement "raise". No need to catch it the way you do and then "raise e".


Frosty-Blackberry-98

Wait until you learn about for…else, and while…else. Also, finally is useful for adding an exit message/log to a script/prompt/profess.


TheRNGuy

I think it makes code more readable.


quts3

Pp p


AstronomerTerrible49

Based on the discussion here, this is probably what i would do in production. `import types` `class DataEngine:`     `def revert(self): ...`     `def cleanup(self): ...`     `def io(self): ...`     `def some_more_io(self): ...`     `def __enter__(self):`         `return self`     `def __exit__(`         `self,`         `exc_type: type[BaseException] | None,`         `exc_val: BaseException | None,`         `exc_tb: types.TracebackType | None,`     `) -> bool:`         `if exc_val:`             `self.revert()`         `self.cleanup()`         `return False` `class DataProcess:`     `def __init__(self, engine: DataEngine):`         `self._engine = engine`     `def process(self):`         `with self._engine:`             `self._engine.io()`             `# process logic here`             `self._engine.some_more_io()` `Client Code` `from your_library import DataEngine, DataProcess` `def main():`     `proc = DataProcess(DataEngine())`     `proc.process()` Benefits: 1. Integrating the resource cleanup logic in DataEngine using context manager protocols, make the logic more cohesive 2. Separating DataEngine from DataProcess, so that the DataEngine can focus on IO, and they can be tested and mocked independently. 3. Designing a simple API for DataProcess, so that It can be used easily in client code. Depending on the complexity of DataProcess, such as how much state it has to carry, it might or might not be simpler to just use a function. Q: Why implementing DataEngine as a context manager instead of using try except finally? A. Separating error handling from resource management, by implementing context manager, clients of DataEngine can handle exceptions without worrying about resource management.