Sometimes I have a cascade of different things I can try to accomplish a task, e. g. If I need to get a record I can first try to find the record, and if this fails, I can c
You could break that into multiple functions ?
def handle_missing():
try:
record = create_record()
except CreateFailed:
record = tape
logger.info("Using a tape now")
else:
logger.info("Created a new record")
return record
def get_record():
try:
record = find_record()
except NoSuchRecord:
record = handle_missing()
else:
logger.info("Record found")
return record
And then you'd use it like,
record = get_record()
You can use a for
loop to successively try variants:
for task, error in ((find_record, NoSuchRecord), (create_record, CreateFailed)):
try:
result = task()
except error:
continue
else:
break
else:
# for..else is only entered if there was no break
result = tape
If you need an else
clause, you can provide it as a separate function:
for task, error, success in (
(find_record, NoSuchRecord, lambda: logger.info("Record found")),
(create_record, CreateFailed, lambda: logger.info("Created a new record"))
):
try:
result = task()
except error:
continue
else:
success()
break
else:
result = tape
logger.info("Using a tape now")
Take note that the default case tape
is not part of the variants - this is because it has no failure condition. If it should execute with the variants, it can be added as (lambda: tape, (), lambda: None)
.
You can put this all into a function for reuse:
def try_all(*cases):
for task, error, success in cases:
try:
result = task()
except error:
continue
else:
success()
return result
try_all(
(find_record, NoSuchRecord, lambda: logger.info("Record found")),
(create_record, CreateFailed, lambda: logger.info("Created a new record")),
(lambda: tape, (), lambda: logger.info("Using a tape now")),
)
In case the tuples seem difficult to read, a NamedTuple
can be used to name the elements. This can be mixed with plain tuples:
from typing import NamedTuple, Callable, Union, Tuple
from functools import partial
class Case(NamedTuple):
task: Callable
error: Union[BaseException, Tuple[BaseException, ...]]
success: Callable
try_all(
Case(
task=find_record,
error=NoSuchRecord,
success=partial(logger.info, "Record found")),
(
create_record, CreateFailed,
partial(logger.info, "Created a new record")),
Case(
task=lambda: tape,
error=(),
success=partial(logger.info, "Using a tape now")),
)
I think following code is more readable and clean. Also I am sure in real problem we need some parameters to be sent to "find_record" and "create_record" functions like id, some, values to create new record. The factory solution need those parameters also be listed in tuple
def try_create(else_return):
try:
record = create_record()
except CreateFailed:
record = else_return
logger.info("Using a tape now")
else:
logger.info("Created a new record")
def try_find(else_call= try_create, **kwargs):
try:
record = find_record()
except NoSuchRecord:
try_create(**kwargs)
else:
logger.info("Record found")
try_find(else_call=try_create, else_return=tape)