Python 如何在嵌套的try-except-else子句中提高代码的清晰度?
有时我会有一系列不同的事情来完成一项任务,例如。G如果我需要得到一条记录,我可以先尝试找到该记录,如果失败,我可以创建丢失的记录,如果失败,我可以使用磁带代替 失败通过抛出代码需要捕获的异常来表示 在Python中,这类似于:Python 如何在嵌套的try-except-else子句中提高代码的清晰度?,python,exception,Python,Exception,有时我会有一系列不同的事情来完成一项任务,例如。G如果我需要得到一条记录,我可以先尝试找到该记录,如果失败,我可以创建丢失的记录,如果失败,我可以使用磁带代替 失败通过抛出代码需要捕获的异常来表示 在Python中,这类似于: 试试看: 记录=查找记录() 除无此记录外: 尝试: 记录=创建_记录() 除CreateFailed外: 记录=磁带 这已经有了堆积压痕的缺点。如果我有五个选项,那么这段代码看起来不太好 但是当try-除了子句之外,还有else子句时,我发现问题更大: 试试看: 记录
试试看:
记录=查找记录()
除无此记录外:
尝试:
记录=创建_记录()
除CreateFailed外:
记录=磁带
这已经有了堆积压痕的缺点。如果我有五个选项,那么这段代码看起来不太好
但是当try
-除了子句之外,还有else
子句时,我发现问题更大:
试试看:
记录=查找记录()
除无此记录外:
尝试:
记录=创建_记录()
除CreateFailed外:
记录=磁带
logger.info(“现在使用磁带”)
其他:
logger.info(“创建了新记录”)
其他:
logger.info(“找到记录”)
find_record()
和相应的record found
消息之间的距离尽可能远,这使得代码很难读取。(将else
子句的代码直接移动到try
子句中只是一个选项,前提是该代码肯定不会引发except
语句中捕获的异常之一,因此这不是通用解决方案。)
同样,这种丑陋随着嵌套级别的增加而变得更糟
有没有更好的方法将其放入Python代码中
在不改变行为和
同时将一个主题的try
和except
子句紧密地放在一起和/或
也许还可以避免堆积嵌套和缩进
你可以把它分解成多个函数
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()
您可以使用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")
请注意,默认情况下的磁带不是变体的一部分-这是因为它没有故障条件。如果它应该与变量一起执行,则可以添加为(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")),
)
我认为下面的代码更具可读性和简洁性。另外,我确信在实际问题中,我们需要发送一些参数来“find_record”和“create_record”函数,比如id,some,value来创建新记录。工厂解决方案还需要在元组中列出这些参数
def try\u create(否则返回):
尝试:
记录=创建_记录()
除CreateFailed外:
记录=否则返回
logger.info(“现在使用磁带”)
其他:
logger.info(“创建了新记录”)
def try_find(else_call=try_create,**kwargs):
尝试:
记录=查找记录()
除无此记录外:
尝试创建(**kwargs)
其他:
logger.info(“找到记录”)
try\u find(else\u call=try\u create,else\u return=tape)
将其分为两个功能?您是否承诺使用异常来表示异常的功能结果?如果<代码> FordOrthys< /Cord>和<代码> CREATETIORION < /COD>返回<代码> No.<代码>,而不是引发异常,这可能会为代码块打开一些设计可能性。如果有相关的故障条件,您可能会考虑创建自定义ErrorHandler。我不认为有一个一般的答案。设计将完全取决于每个函数返回和/或可以产生的内容。@chepner我经常碰到这个问题,所以我希望有一个通用的方法,而不是只在一种情况下工作的专门方法。要捕获的异常可能是诸如索引器
或值错误
之类的一般性事件,因此,我希望尽量减少try
-子句,以避免无意中捕获其他内容。您可以使用第三个工厂lambda:tape
,它消除了for
循环中else
子句的需要。一般情况下,当然可能会引发问题,但实际上,您可以通过()
作为语句的值,除了
之外,它什么也抓不到。@chepner,这里有一个答案:)为什么你仍然说它无法回答?实际上我喜欢这个。如果工厂很复杂,我想我会把它们分解成单个函数(比如@hansolo)。如果不同情况下的代码应该有一些内部函数的副作用,如设置局部变量等,这只会带来麻烦。但是,这些都需要输入到返回值中。@MisterMiyagi同样,如果要在for
循环中添加else
子句,您不必从result=tape
开始;把它也放在else
子句中。是的,我明白了,它将每个缩进级别分解为一个函数:)这似乎很简单,是的。是的,如果你看到自己编写try..except..else
嵌套,你几乎总是可以将其分解为多个函数,代码将更具可读性,更易于维护:)