In some cases it may be desirable to keep exception handling in a helper
that returns :obj:`None` on error. In the past, the project used an
optional error argument that defaulted to :obj:`True` that indicated
that exceptions should be raised. Callers could pass error=False to
instruct the function to return :obj:`None` instead.
With Python's
typing
annotations, these routines must be annotated as returning an
Optional
value. While the
@overload
decorator allows us to associate return types with specific argument types
and counts, there is no way to associate a return type with specific
argument values, like error=False.
A function annotated as returning an Optional value affects the implied
types of the variables used to assign the result. Every caller of such
a routine would need to check the result against :obj:`None` in order to
drop the Optional annotation from the type. Even when we know the
function cannot return :obj:`None` when passed error=True.
The way we handle this is to have separate functions for each case so that callers which will never have a :obj:`None` value returned do not need to check it.
Here are a few examples:
from typing import Optional
import gdb
def new_routine(val: gdb.Value) -> str:
if some_condition:
raise RuntimeError("something bad happened")
return val.string()
def new_routine_safe(val: gdb.Value) -> Optional[str]:
try:
return new_routine(val)
except RuntimeError:
return Nonefrom typing import Optional
import gdb
def some_existing_routine(val: gdb.Value, error: bool = True) -> Optional[str]:
if some_condition:
if error:
raise RuntimeError("something bad happened")
return None
return val.string()
def new_routine(val: gdb.Value) -> str:
print("do something")
ret = some_existing_routine(val)
# This is required to drop the Optional annotation
if ret is None:
raise RuntimeError("some_existing_routine can't return None")
return ret
def new_routine_safe(val: gdb.Value) -> Optional[str]:
return some_existing_routine(val, False)