Time ago I wrote about how nice Kotlin try expressions feel to me, and about emulating them in JavaScript using a function. Obviously we can also emulate them in Python, though the lack of statement lambdas restrict the cases where they can be used in an elegant way (without having to declare a separate function before). Let's see a simple implementation (a more complex one would include a finally function and different handlers for different exception types):
def do_try(action: Callable, exceptions: BaseException | list[BaseException] | None = Exception, on_except: Any | None = None) -> Any:
"""
simulate 'try expressions'
on_except can be a value or a Callable (that receives the Exception)
"""
try:
return action()
except exceptions as ex:
return on_except(ex) if (on_except and callable(on_except)) else on_except
There's a rejected PEP that proposed a pretty nice syntax:
cond = (args[1] except IndexError: None)
but we know how anti-functional programming some Python deciders are...
Kotlin try expressions are obviously more powerful as we can have multiple statements, not just expressions, in its try-catch-finally blocks, but at the same time for simple cases (like the above proposal) are more verbose (because of the brackets).
There are two cases where the simple do_try function (with its defaults) that I showed above really shines, for "try and forget" cases and for "default if it fails" cases.
# try and forget
do_try(lambda: os.unlink(aux_file1))
# default if it fails
v = do_try(lambda: int(txt), on_except=-1)
It's interesting that python provides a context manager for "try and forget" cases:
from contextlib import suppress
with suppress(Exception):
os.unlink(aux_file1)
That context manager really shines when combined with a contextlib.nullcontext, so that we can easily decide to ignore or not to ignore exceptions:
def myfunction(arg, ignore_exceptions=False):
# nullcontext has no effect, so it won't ignore exceptions
cm = contextlib.suppress(Exception) if ignore_exceptions else contextlib.nullcontext()
with cm:
# Do something
In Kotlin, apart from the expressive try-catch expressions we have the runCatching function. This function can be used instead of try-catch, providing an alternative, more functional way of dealing with errors. That's a complex beast that I have not properly explored yet (and even less more purely functional approaches like Arrow), so what interests me about runCatching is using it just for "try and forget" situations and for "default if it fails" situations. I copy-paste 2 samples from this nice article.
// try and forget
fun fireAndForget() {
try {
riskyFunction()
} catch (t: Throwable) {
// Ignore
}
}
fun fireAndForget() {
runCatching { riskyFunction() }
}
// default if it fails:
fun parseNumberWithDefault(input: String): Int {
return try {
input.toInt()
} catch (t: Throwable) {
0 // Default value for invalid numbers
}
}
// it's cleaner using runCatching:
fun parseNumberWithDefault(input: String): Int {
return runCatching { input.toInt() }.getOrElse { 0 }
}
No comments:
Post a Comment