Python: try-except as an Expression?
I find myself having this sort of pattern over and over:
variable = ""
try:
variable = ... do some file loading stuff ...
except:
variable = ""
Is there any way t开发者_如何学编程o condense this into a single expression? Like with if-else statements you can turn:
variable = ""
if something:
variable = somethingelse
else:
variable = ""
into
variable = somethingelse if something else ""
Is there any equivalent thing for try-catch?
Since agf already provided the approach I'd recommend, here's a version of his routine with a couple of minor enhancements:
def try_except(success, failure, *exceptions):
try:
return success()
except exceptions or Exception:
return failure() if callable(failure) else failure
This version:
Lets you specify exactly which exceptions will be caught as additional optional arguments. You should always catch the minimum set of exceptions that will do the job and let exceptions you can't handle bubble up to the caller.
Supports the use of a plain value as well as a function for the failure value. This saves you having to use a lambda in a lot of cases. (Of course, instead of
lambda: ''
you can just usestr
.)
def try_except(success, failure):
try:
return success()
except:
return failure()
variable = try_except(do_some_file_loading_stuff, lambda: '')
I think the code is self explanatory. It returns the value returned by success
unless there is an error, then it returns the value returned by failure
. If do_some_file_loading_stuff
is an expression rather than just a function call, wrap it in a lambda
as well.
Edit: @kindall and I improved his version a bit so it's just as fast as mine, can be called exactly the same if you want, has more features, and is the same number of lines. Use it!
def try_except(success, failure, *exceptions):
try:
return success()
except exceptions or Exception:
return failure() if callable(failure) else failure
Here's a context manager that provides a little bit of a shortcut:
from contextlib import contextmanager
@contextmanager
def catch(*exceptions, **kwargs):
try:
yield kwargs.get("default", None)
except exceptions or Exception:
pass
Usage:
with catch(ZeroDivisionError, default=0) as x:
x = 3 / 0 # error
print x # prints 0, the default
The basic idea here is that the context manager returns whatever default value you pass it, which is then assigned to the variable you specified in the with
statement's as
clause. Then, inside the context, you execute a statement that tries to assign to that same variable. If it raises an exception, the context manager catches and silently ignores it, but since the assignment didn't happen, the default value remains.
Might be particularly useful for multi-line calculations that nevertheless result in a single value.
Unfortunately, no, there is no language construct for it and I do not know any really legible and concise idiom either. I always wanted something like that. Some time ago someone gave me reasons to Python does not have something like variable = function_cal() except ""
but they were not very convincing and I still miss this language construct :)
Obviously, the latter way of doing it is more succinct and is preferred by many programmers like you.
While the language itself doesn't provide a "one-line try-except-catch" for whatever the reasons, all good and necessary, I presume, you can generally accomplish this, by changing the program, a little; like for example:
try:
a_var = a_dict.get('abcd')
except a_dict.KeyError:
a_var = ''
into:
a_var = a_dict.get('abcd',default='')
and similarly for the DB queries;
try:
a_qs = Model.objects.get(id=42)
except Model.DoesNotExist:
a_qs = Model.objects.create(id=42)
with
a_qs = Model.objects.get_or_create(id=42,**kwargs)
and add similar API's to your own programs where possible. try-except is rather "cheap" in Python and is exception based programming is preferred over the "check-first" approach generally suggested in Java like languages because of the expensive nature of the exception handling. So, you should rather wrap this "catching thing" in a method/function and call that every where, like for example the dict and other constructs do.
There is no easy way to simplify a try/catch statement like the if/else example, but I want to point out that python's "with" statement introduced in python2.5 made a lot of interfaces to file/db io try catch statements simpler and exception safe. IO operations tend to be where a lot of try/catch statements are used.
with open("myfile.txt", "r") as f:
# Do stuff with f
instead of
try:
f = open("myfile.txt", "r")
# Do stuff with f
except:
pass
finally:
if f: f.close
Inspired by with-handlers in Racket:
def with_handlers(handlers: Dict[Type[Exception], Callable[[Exception], S]], f: Callable[[], T]) -> Union[S, T]:
try:
return f()
except tuple(handlers.keys()) as err:
return handlers[type(err)](err)
handlers
contains all expected exceptions and their corresponding handlers.
And here is a catch
function, which also supports else and final clauses:
def catch(
try_clause: Callable[[], T],
except_clauses: Dict[Type[Exception], Callable[[Exception], S]],
else_clause: Optional[Callable[[T], R]] = None,
final_clause: Optional[Callable[[Union[T, S]], None]] = None) -> Union[R, S, T]:
try:
ret = try_clause()
except tuple(except_clauses.keys()) as err:
handled = except_clauses[type(err)](err)
return handled
else:
return ret if else_clause is None else else_clause(ret)
finally:
if final_clause is not None:
try:
final_clause(ret)
except NameError:
final_clause(handled)
In Python, try statements do not introduce their own scope,
but here try_clause
is a function, so else_clause
needs a parameter to receive information from try_clause
.
Similarly, final_clause
needs two parameters.
精彩评论