【问题标题】:How to improve code clarity in nested try-except-else clauses?如何提高嵌套 try-except-else 子句中的代码清晰度?
【发布时间】:2019-09-17 12:55:18
【问题描述】:

有时我有一系列不同的事情可以尝试完成一项任务,例如。 G。如果我需要获取记录,我可以先尝试查找记录,如果失败,我可以创建丢失的记录,如果也失败,我可以使用磁带代替。

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

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

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

这已经有堆积压痕的缺点。如果我有五个选项,这段代码就不会好看。

但是当try-except 子句也有else 子句时,我发现问题更大:

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

find_record() 和对应的Record found 消息距离尽可能远,这使得代码难以阅读。 (将else 子句的代码直接移动到try 子句中只是一种选择,如果此代码肯定不会引发except 语句中捕获的异常之一,因此这不是通用解决方案。)

再一次,这种丑陋随着嵌套层数的增加而变得更糟。

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

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

【问题讨论】:

  • 把它分成两个函数?
  • 您是否致力于使用异常来表示异常的函数结果?如果 find_recordcreate_record 在失败时返回 None 而不是引发异常,这可能会为您的代码块打开一些设计可能性。
  • 如果有相关的失败情况可以考虑创建一个自定义的ErrorHandler。
  • 我不认为有一个普遍的答案。设计将由每个函数返回和/或可以引发的确切内容决定。
  • @chepner 我经常偶然发现这件事,所以我想有一个通用的方法,而不是只在一种情况下有效的专门方法。要捕获的异常可能是IndexErrorValueError 之类的一般细项,因此我想将try 子句保持尽可能小,以避免无意中捕获其他内容。

标签: python exception


【解决方案1】:

您可以使用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

如果您需要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")

请注意,默认情况 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")),
)

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

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

【讨论】:

  • 您可以拥有第三个工厂lambda: tape,它消除了for 循环上的else 子句的需要。
  • 一般情况当然可以引发问题,但实际上,您可以将() 作为except 语句的值传递,然后它不会捕获任何内容。
  • @chepner,这是一个答案 :) 为什么你仍然说它无法回答?
  • 其实我喜欢这个。如果工厂很复杂,我想我会将它们分解为单个功能(如@hansolo 提议的)。如果不同情况的代码应该具有一些内部函数副作用,例如设置局部变量等,这只会造成麻烦。但是这一切都需要进入返回值。
  • @MisterMiyagi 另外,如果您要在for 循环中添加else 子句,则不必以result = tape 开头;把它放在else 子句中。
【解决方案2】:

你可以把它分解成多个函数吗?

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

然后你会像这样使用它,

record = get_record()

【讨论】:

  • 是的,我明白了,它将每个缩进级别分解为一个函数:) 看起来很简单,是的。
  • 是的,如果你看到自己在编写try..except..else 嵌套,你几乎总是可以将其分解为多个函数,并且代码将更具可读性和易于维护:)
【解决方案3】:

我认为以下代码更具可读性和简洁性。此外,我确信在实际问题中,我们需要将一些参数发送到“find_record”和“create_record”函数,如 id、some、values 来创建新记录。工厂解决方案需要这些参数也列在元组中

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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-21
    • 1970-01-01
    • 2014-08-06
    相关资源
    最近更新 更多