如何提高嵌套的try-except-else子句中的代码清晰度?

Alf*_*lfe 3 python exception

有时我会尝试各种不同的事情来尝试完成一项任务,例如,如果需要获取记录,我可以先尝试查找记录,如果失败,则可以创建丢失的记录,并且如果失败也可以,我可以改用磁带。

失败是通过抛出我的代码需要捕获的异常来表示的。

在Python中,这看起来像这样:

try:
  record = find_record()
except NoSuchRecord:
  try:
    record = create_record()
  except CreateFailed:
    record = tape
Run Code Online (Sandbox Code Playgroud)

这已经具有堆积凹痕的缺点。如果我有五个选择,那么这段代码看起来不太好。

但是,当- 子句中也有else子句时,我会发现问题更加严重:tryexcept

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")
Run Code Online (Sandbox Code Playgroud)

find_record()和相应的Record found消息是尽可能远离可能这使得它很难读代码。(将else子句的代码直接移到try子句中只是一种选择,如果该代码绝对不会引发except语句中捕获的异常之一,那么这不是一般的解决方案。)

同样,每增加一层嵌套,这种丑陋就变得更糟。

有没有更好的方法将其放入Python代码

  1. 而不改变行为和
  2. 同时将一个主题的tryexcept子句保持在一起和/或
  3. 也许还避免堆积的嵌套和缩进?

Mis*_*agi 6

您可以使用for循环来依次尝试变体:

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
Run Code Online (Sandbox Code Playgroud)

如果需要else子句,可以将其作为单独的函数提供:

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")
Run Code Online (Sandbox Code Playgroud)

注意,默认情况下tape不会变体的一部分-这是因为它没有失败的情况。如果应使用变体执行,则可以将其添加为(lambda: tape, (), lambda: None)


您可以将所有这些放到一个函数中以供重用:

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")),
)
Run Code Online (Sandbox Code Playgroud)

如果元组看起来难以阅读,NamedTuple则可以使用a来命名元素。这可以与普通元组混合:

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")),
)
Run Code Online (Sandbox Code Playgroud)

  • @MisterMiyagi同样,如果要在`for`循环中添加`else`子句,则不必以`result = tape'开始;也将其放在`else`子句中。 (2认同)