IMHO I think of raise as control flow, and I prefer control flow to be very explicit.
Personally I view the case of single-line conditioned assignment
value = a() if condition else b()
as “A value if made and assigned“, so conceptually its is more or less like
value = something
When I look at that, it is very different thing from the control flow version
if condition:
a()
else
b()
Which is “The function either does one thing or the other“
if condition:
arbitrary things can happen here, including early return
else
different arbitrary things can happen here, including early return
So for me making raise a function is kind of deceptive, because I wouldn’t expect a value = something kind of code to deliberately raise an error.
Obviously, errors can be raised anywhere (like in dictionary lookups), but my expectation is that there are two kinds of error raising a function can make:
- Expected errors, where a valid (or at least expected) input can lead to an error
- Unexpected error, where an invalid/unexpected input breaks the assumptions of the function
I feel expected errors are control flow, and therefore should be very explicit, and unexpected error should just be “ignored” (better to beg for forgiveness than ask for permission and such). A raise function to me is too implicit for the former and too explicit for the latter
# Example: I quickly scan some code and this is roughly what I imagine
def function(important, not_important=None):
"""It's a bug if important doesn't satisfy blah blah blah"""
not_important = default # should work if I satisfied blah blah blah
condition = something # should work if I satisfied blah blah blah
if condition:
raise Error # here is an exit point
return # here is an exit point
# Actual example code
def get_message(request, key=None):
"""Get message from request dictionary
Expect a request dictionary with message in 'key' {key: message, ...} with an 'okay' key. default key is 'message'.
Or not, I'm a function, not a cop.
"""
key = "message" if key is None else key # okay, roughly as expected
message = request[key] or raise(ValueError, "empty message") # Actually this is also an exit point if I have an empty message
if not message ['okay']: # Can raise and exit, but implicit: If I call this function with a badly-formatted message, I guess it's a bug.
raise ValueError(message) # okay, roughly as expected
return message # okay, roughly as expected
# What I would expect:
def get_message_expected_errors(request, key=None):
"""Get message in 'key' (default 'message') from request dictionary
If a request is without message in 'key', or the ,essage is empty, or the messgae doesn't have 'okay' key, raise ValueError.
If the message 'okay' is False, raise ValueError
"""
...
message = request[key if key is not None else 'message']
try:
message = request[key]
if not message :
raise ValueError(f"Empty message in {request=}")
if 'okay' not in message:
raise ValueError(f"Badly formatted {message=}")
except KeyError:
raise ValueError(f"Could not find message by {key!r} in {request=}")
if not message['okay']:
raise ValueError(message )
return message
def get_message_implicit_error(request, key=None):
"""Get message in 'key' (default 'message') from request dictionary
We expect a request with a message and that message to have an 'okay' signal
If the message 'okay' is False, raise ValueError
"""
message = request[key if key is not None else 'message']
if not message ['okay']:
raise ValueError(message )
return message