Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
212 views
in Technique[技术] by (71.8m points)

python - How to improve code clarity in nested try-except-else clauses?

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 create the missing record, and if this also fails, I can use a tape instead.

Failing is represented by throwing an exception my code needs to catch.

In Python this looks something like this:

try:
  record = find_record()
except NoSuchRecord:
  try:
    record = create_record()
  except CreateFailed:
    record = tape

This already has the disadvantage of piling-up indentations. If I have five options, this code won't look good.

But I find it even more problematic when there are also else clauses to the try-except clauses:

try:
  record = find_record()
except NoSuchRecord:
  try:
    record = create_record()
  except CreateFailed:
    record = tape
    logger.info("Using a tape now")
  else:
    logger.info("Created a new record")
else:
  logger.info("Record found")

The find_record() and the corresponding Record found message are as far apart as possible which makes it hard to read code. (Moving the code of the else clause directly into the try clause is only an option if this code is definitely not raising one of the exceptions caught in the except statement, so this is no general solution.)

Again, this ugliness gets worse with each added level of nesting.

Is there a nicer way to put this into Python code

  1. without changing the behavior and
  2. while keeping the try and the except clauses of one topic closely together and/or
  3. maybe also avoiding the piling-up nesting and indenting?
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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")),
)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...