为什么“except: pass”是一种不好的编程习惯?

2024-11-28 08:37:00
admin
原创
225
摘要:问题描述:我经常看到其他 Stack Overflow 问题上关于不鼓励使用的评论except: pass。为什么这样不好?有时我只是不关心错误是什么,只想继续写代码。try: something except: pass 为什么使用except: pass块不好?什么让它变得不好?是因为我p...

问题描述:

我经常看到其他 Stack Overflow 问题上关于不鼓励使用的评论except: pass。为什么这样不好?有时我只是不关心错误是什么,只想继续写代码。

try:
    something
except:
    pass

为什么使用except: pass块不好?什么让它变得不好?是因为我pass犯了错误还是我犯了except任何错误?


解决方案 1:

正如您正确猜测的那样,它有两个方面:通过在之后不指定任何异常类型来捕获任何except错误,然后简单地传递它而不采取任何操作。

我的解释“有点”长——所以 tl;dr 可以分解为以下几点:

  1. 不要捕获任何错误。始终指定您准备恢复的异常,并且只捕获那些异常。

  2. 尽量避免传入 except 块。除非明确需要,否则这通常不是一个好兆头。

但让我们详细讨论一下:

不要捕捉任何错误

使用try块时,您通常会这样做,因为您知道有可能会抛出异常。因此,您也已经大致了解了什么可能中断以及可能抛出什么异常。在这种情况下,您会捕获异常,因为您可以从中积极恢复。这意味着您已为异常做好准备,并且有在发生该异常时要遵循的一些替代计划。

例如,当您要求用户输入一个数字时,您可以使用 转换输入,int()这可能会引发ValueError。您只需要求用户再试一次就可以轻松恢复,因此捕获ValueError并再次提示用户将是一个合适的计划。另一个例子是,如果您想从文件中读取一些配置,而该文件恰好不存在。因为它是一个配置文件,所以您可能有一些默认配置作为后备,所以该文件不是必需的。所以捕获FileNotFoundError并简单地应用默认配置在这里是个好计划。现在在这两种情况下,我们都有一个非常具体的异常,我们期望并且有一个同样具体的计划来从中恢复。因此,在每种情况下,我们都只明确指出except 那个特定的异常。

然而,如果我们捕获所有异常,那么除了我们准备恢复的异常之外,还可能遇到意想不到的异常,而这些异常我们确实无法恢复;或者不应该恢复。

让我们以上面的配置文件示例为例。如果文件丢失,我们只需应用默认配置,并可能在稍后决定自动保存配置(因此下次文件就会存在)。现在假设我们得到的是IsADirectoryError,或PermissionError。在这种情况下,我们可能不想继续;我们仍然可以应用默认配置,但稍后将无法保存文件。并且用户很可能也想要自定义配置,因此可能不希望使用默认值。因此,我们希望立即告知用户,并且可能也会中止程序执行。但这不是我们想在某个小代码部分深处做的事情;这是应用程序级别的重要内容,因此应该在顶部处理 - 因此让异常冒泡。

Python 2 习惯用法文档中还提到了另一个简单的例子。这里,代码中存在一个简单的拼写错误,导致它中断。因为我们捕获了每个异常,所以我们也捕获了NameErrors和SyntaxErrors。这两个错误都是我们在编程时都会犯的错误,也是我们在交付代码时绝对不想犯的错误。但是因为我们也捕获了它们,所以我们甚至不知道它们发生在那里,并且失去了正确调试它的任何帮助。

但也有更危险的异常,我们不太可能做好准备。例如,SystemError通常很少发生,我们无法真正计划;这意味着发生了一些更复杂的事情,可能会阻止我们继续当前任务。

无论如何,您不太可能在代码的一小部分中为所有事情做好准备,所以您实际上应该只捕获您准备好的异常。有些人建议至少捕获,Exception因为它不会包括像SystemExitKeyboardInterrupt这样设计用来终止应用程序的内容,但我认为这仍然太不具体了。只有一个地方我个人接受捕获Exception任何异常,那就是在单个全局应用程序级异常处理程序中,该处理程序的唯一目的是记录我们没有准备好的任何异常。这样,我们仍然可以保留尽可能多的有关意外异常的信息,然后我们可以用这些信息扩展我们的代码以明确处理这些异常(如果我们可以从中恢复)或者 - 在出现错误的情况下 - 创建测试用例以确保它不会再次发生。但当然,这只有当我们只捕获我们已经预料到的异常时才有效,所以我们没有预料到的异常自然会冒出来。

尽量避免传入 except 块

当明确捕获一小部分特定异常时,很多情况下我们什么都不做就没问题了。在这种情况下,只要有就except SomeSpecificException: pass行了。但大多数情况下情况并非如此,因为我们可能需要一些与恢复过程相关的代码(如上所述)。例如,这可以是再次重试操作,或者设置默认值。

但如果不是这样,例如,因为我们的代码已经构造为重复直到成功,那么只需通过就足够了。以上面的例子为例,我们可能想要求用户输入一个数字。因为我们知道用户不喜欢我们要求他们做的事情,所以我们可能首先将其放入循环中,因此它可能看起来像这样:

def askForNumber ():
    while True:
        try:
            return int(input('Please enter a number: '))
        except ValueError:
            pass

因为我们一直尝试直到没有异常抛出,所以我们不需要在 except 块中做任何特殊的事情,所以这没问题。但当然,有人可能会说我们至少想向用户显示一些错误消息,告诉他为什么必须重复输入。

不过,在许多其他情况下,仅仅传入一个异常except就表明我们并没有真正为捕获的异常做好准备。除非这些异常很简单(例如ValueErrorTypeError),并且我们可以通过的原因很明显,否则请尽量避免直接通过。如果真的没什么可做的(并且您对此非常确定),那么请考虑添加注释来说明为什么会这样;否则,请扩展 except 块以实际包含一些恢复代码。

except: pass

然而,最糟糕的情况是两者兼而有之。这意味着我们愿意捕获任何错误,尽管我们绝对没有为此做好准备,而且我们也不会对此采取任何措施。您至少希望记录错误,并且可能重新引发它以终止应用程序(在发生 MemoryError 之后,您不太可能像往常一样继续运行)。只是通过它不仅会使应用程序保持某种程度的活力(当然取决于您在哪里捕获),而且还会丢弃所有信息,从而无法发现错误 — 如果您不是发现错误的人,情况尤其如此。


因此,底线是:只捕获您真正期望并准备恢复的异常;所有其他异常都可能是您应该修复的错误,或者您无论如何都没有准备好。如果您真的不需要对它们做些什么,传递特定的异常是可以的。在所有其他情况下,这只是一种假设和懒惰的表现。你肯定想解决这个问题。

解决方案 2:

这里的主要问题是它忽略了所有错误:内存不足、CPU 超载、用户想要停止、程序想要退出、Jabberwocky 正在杀死用户。

这太过分了。你脑子里想着“我想忽略这个网络错误”。如果出现意外错误,那么你的代码会默默继续运行,并以完全不可预测的方式中断,没有人可以调试。

这就是为什么你应该限制自己只忽略某些错误,而让其余的错误通过。

解决方案 3:

执行伪代码实际上甚至不会给出任何错误:

try:
    something
except:
    pass

好像它是一段完全有效的代码,而不是抛出一个NameError。我希望这不是你想要的。

解决方案 4:

为什么“except: pass”是一种不好的编程习惯?

这有什么不好吗?

try:
    something
except:
    pass

这会捕获所有可能的异常,包括GeneratorExitKeyboardInterruptSystemExit- 这些都是您可能不想捕获的异常。它与捕获相同BaseException

try:
    something
except BaseException:
    pass

旧版本的文档这样说:

由于 Python 中的每个错误都会引发异常,使用except:会使许多编程错误看起来像运行时问题,从而阻碍调试过程。

Python 异常层次结构

如果你捕获了一个父异常类,你也会捕获它们的所有子类。只捕获你准备处理的异常会更优雅。

这是 Python 3异常层次结构- 您真的想捕获它们全部吗?:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
           +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

不要这样做

如果您使用这种形式的异常处理:

try:
    something
except: # don't just do a bare except!
    pass

那么你将无法something使用 Ctrl-C 中断你的代码块。你的程序将忽略代码块内所有可能的异常try

这是另一个具有相同不良行为的示例:

except BaseException as e: # don't do this either - same as bare!
    logging.info(e)

相反,尝试仅捕获您知道正在寻找的特定异常。例如,如果您知道在转换时可能会出现值错误:

try:
    foo = operation_that_includes_int(foo)
except ValueError as e:
    if fatal_condition(): # You can raise the exception if it's bad,
        logging.info(e)   # but if it's fatal every time,
        raise             # you probably should just not catch it.
    else:                 # Only catch exceptions you are prepared to handle.
        foo = 0           # Here we simply assign foo to 0 and continue. 

用另一个例子进一步解释

您这样做可能是因为您一直在进行网页抓取并得到一个结果UnicodeError,但是因为您使用了最广泛的异常捕获,所以您的代码(可能存在其他根本缺陷)将尝试运行至完成,从而浪费带宽、处理时间、设备磨损、内存耗尽、收集垃圾数据等。

如果其他人要求你完成工作以便他们能够依赖你的代码,我理解你不得不处理所有事情的感觉。但是如果你愿意在开发过程中大声失败,你就有机会纠正可能只是间歇性出现的问题,但从长远来看,这将是代价高昂的错误。

通过更精确的错误处理,您的代码可以更加健壮。

解决方案 5:

>>> import this

《Python 之禅》,作者:Tim Peters

美丽胜过丑陋。

明确胜过隐晦。

简单胜过复杂。

复杂胜过繁琐。

扁平胜过嵌套。稀疏胜过

密集。

可读性很重要。

特殊情况不足以特殊到打破规则。尽管

实用性胜过纯粹性。

错误永远不应该默默无闻。

除非明确地被压制。

面对歧义,拒绝猜测的诱惑。

应该有一种——最好只有一种——明显的方法来做到这一点。

尽管除非你是荷兰人,否则这种方法一开始可能并不明显。

现在总比没有好。

尽管没有往往比现在更好如果

实现很难解释,那不是一个好主意。

如果实现很容易解释,那可能是一个好主意。

命名空间是一个非常棒的主意——让我们多做些这样的事情吧!

所以,这是我的观点。每当你发现错误时,你应该做一些事情来处理它,即把它写在日志文件或其他东西中。至少,它会告诉你曾经有一个错误。

解决方案 6:

您至少应该使用except Exception:以避免捕获诸如SystemExit或 之类的系统异常KeyboardInterrupt。这是文档链接。

通常,您应该明确定义要捕获的异常,以避免捕获不需要的异常。您应该知道要忽略哪些异常。

解决方案 7:

首先,它违反了Python 之禅的两个原则:

  • 明确优于隐含

  • 错误不应该悄无声息地发生

它的意思是,你故意让错误悄无声息地通过。此外,你甚至不知道到底发生了哪个错误,因为except: pass会捕获任何异常。

其次,如果我们试图脱离 Python 的禅宗,只从理智的角度来谈论,你应该知道,使用except:pass会让你对系统一无所知,无法控制。经验法则是,如果发生错误,就引发异常,并采取适当的措施。如果你事先不知道应该采取什么措施,至少在某处记录错误(最好重新引发异常):

try:
    something
except:
    logger.exception('Something happened')

但是,通常情况下,如果您尝试捕获任何异常,那么您可能做错了什么!

解决方案 8:

第一个原因已经说明过了——它隐藏了您没有预料到的错误。

(#2)-它使其他人难以阅读和理解您的代码。如果您在尝试读取文件时捕获了 FileNotFoundException,那么对于其他开发人员来说,“catch”块应该具有什么功能就非常明显了。如果您没有指定异常,那么您需要额外的注释来解释该块应该做什么。

(#3)-它展示了懒惰的编程。如果你使用通用的 try/catch,这表明你不了解程序中可能出现的运行时错误,或者你不知道 Python 中可能出现哪些异常。捕获特定错误表明你既了解程序,也了解 Python 抛出的错误范围。这更有可能让其他开发人员和代码审阅者信任你的工作。

解决方案 9:

那么,此代码产生什么输出?

fruits = [ 'apple', 'pear', 'carrot', 'banana' ]

found = False
try:
     for i in range(len(fruit)):
         if fruits[i] == 'apple':
             found = true
except:
     pass

if found:
    print "Found an apple"
else:
    print "No apples in list"

现在想象一下try-except代码块包含数百行对复杂对象层次结构的调用,并且本身在大型程序的调用树中间被调用。当程序出错时,您从哪里开始查找?

解决方案 10:

except:pass构造本质上会忽略块中涵盖的代码try:运行时出现的任何和所有异常情况。

这种做法之所以不好,是因为它通常不是您真正想要的。更常见的情况是,一些特定的情况即将出现,而您想将其隐藏起来,但except:pass这种做法太过生硬。虽然它可以完成工作,但也会掩盖您可能没有预料到的其他错误情况,但您可能非常希望以其他方式处理这些错误情况。

这在 Python 中尤为重要,因为根据这种语言的习惯用法,异常不一定是错误。当然,就像在大多数语言中一样,它们通常以这种方式使用。但 Python 尤其偶尔使用它们来实现某些代码任务的替代退出路径,这些路径实际上不是正常运行情况的一部分,但已知会不时出现,甚至在大多数情况下是预期的。SystemExit已经作为一个老例子提到,但现在最常见的例子可能是StopIteration。以这种方式使用异常引起了很多争议,特别是当迭代器和生成器首次引入 Python 时,但最终这个想法占了上风。

解决方案 11:

一般来说,你可以将任何错误/异常归为以下三类之一:

  • 致命错误:不是你的错,你无法阻止它们,也无法从中恢复。你当然不应该忽略它们并继续,让你的程序处于未知状态。只能让错误终止你的程序,你什么也做不了。

  • 愚蠢:你自己的错,很可能是由于疏忽、错误或编程错误。你应该修复错误。同样,你绝对不应该忽视并继续。

  • 外生性:在异常情况下可能会出现这些错误,例如文件未找到连接终止。您应该明确处理这些错误,并且只处理这些错误。

在所有情况下except: pass都只会使您的程序处于未知状态,从而可能造成更大的损害。

解决方案 12:

到目前为止提出的所有意见都是有效的。在可能的情况下,您需要指定要忽略的异常。在可能的情况下,您需要分析导致异常的原因,并且只忽略您想要忽略的异常,而不是其他异常。如果异常导致应用程序“严重崩溃”,那就这样吧,因为知道意外发生的时间比隐瞒问题是否发生更重要。

尽管如此,不要把任何编程实践视为最重要的事情。这是愚蠢的。总是有时间和地点去做忽略所有异常的块。

另一个愚蠢至上的例子是goto运算符的使用。当我还在上学的时候,我们的教授教我们goto运算符只是为了提醒我们永远不要使用它。不要相信有人告诉你永远不要使用 xyz,也不可能存在它有用的场景。总是有的。

解决方案 13:

简单来说,如果抛出了异常或错误,那就说明出了问题。这可能不是什么大问题,但仅仅为了使用 goto 语句而创建、抛出和捕获错误和异常并不是一个好主意,而且很少这样做。99% 的时间里,某个地方都出了问题。

问题需要解决。就像生活中一样,在编程中,如果你只是置问题于不理,并试图忽略它们,它们很多时候不会自行消失;相反,它们会变得越来越大,不断增加。为了防止问题不断增长并在以后再次出现,你要么 1) 消除它,然后清理混乱,要么 2) 遏制它,然后清理混乱。

忽略异常和错误并让它们保持原样是体验内存泄漏、未完成的数据库连接、文件权限不必要的锁定等的好方法。

在极少数情况下,问题非常小,微不足道,而且除了需要 try...catch 块之外,它是自包含的,事后真的没有什么乱七八糟需要清理。这些是这种最佳实践不一定适用的唯一情况。根据我的经验,这通常意味着代码所做的任何事情基本上都是微不足道的,可以忽略不计,而重试尝试或特殊消息之类的东西既不值得增加复杂性,也不值得让线程停滞不前。

在我的公司,规则是几乎总是在 catch 块中执行某些操作,如果您不执行任何操作,则必须始终添加注释并给出不执行该操作的充分理由。当需要执行任何操作时,您绝不能跳过或留下空的 catch 块。

解决方案 14:

在我看来,错误出现是有原因的,听起来很愚蠢,但事实就是如此。好的编程只有在必须处理错误时才会引发错误。另外,正如我前段时间读到的,“pass 语句是一条显示稍后将插入代码的语句”,因此,如果您想要一个空的 except 语句,请随意这样做,但对于一个好的程序来说,会缺少一部分。因为您没有处理您应该处理的事情。出现的异常让您有机会纠正输入数据或更改数据结构,以便这些异常不会再次发生(但在大多数情况下(网络异常、一般输入异常)异常表示程序的下一部分将无法很好地执行。例如,NetworkException 可能表示网络连接中断,程序无法在下一个程序步骤中发送/接收数据。

但是仅对一个执行块使用传递块是有效的,因为您仍然区分异常的类型,因此如果将所有异常块放在一个块中,它就不是空的:

try:
    #code here
except Error1:
    #exception handle1

except Error2:
    #exception handle2
#and so on

可以这样重写:

try:
    #code here
except BaseException as e:
    if isinstance(e, Error1):
        #exception handle1

    elif isinstance(e, Error2):
        #exception handle2

    ...

    else:
        raise

因此,即使带有 pass 语句的多个 except 块也会产生代码,其结构可以处理特殊类型的异常。

解决方案 15:

由于尚未提及,因此最好使用以下样式contextlib.suppress

with suppress(FileNotFoundError):
    os.remove('somefile.tmp')

在这个例子中,在该代码块执行后且没有引发任何异常(除了被抑制的)somefile.tmp之后,将不存在。FileNotFoundError

解决方案 16:

​在编程中处理错误非常重要。你确实需​​要向用户展示出了什么问题。在极少数情况下,你可以忽略错误。这是非常糟糕的编程习惯。

解决方案 17:

我正在构建一个将在数据中心运行的应用程序。它不应生成任何错误或引发任何异常。我的数据中心有一个网络监控系统,其中包括一个 SNMP 陷阱接收器。

try:
    main()
except as e:
    log(str(e))
    send_snmp_trap(str(e))
    raise

除了加注不会发生任何事情因为它是任何可能留下的堆栈的底部。

顺便说一句,这绝不是万能药。有些异常无法捕获。SNMP 不保证交付。YMMV。

解决方案 18:

如果这是不好的做法,“通过”将不是一个选择。如果你有一个从许多地方接收信息的资产,即表单或用户输入,它就会派上用场。

variable = False
try:
    if request.form['variable'] == '1':
       variable = True
except:
    pass

解决方案 19:

我个人更喜欢这个解决方案:

except ValueError as error:
                print(error.args)
                pass

error.args给我一行代码,不会太分散我的注意力,但确实有助于代码审查,特别是当错误有多种原因时,例如

(ValueError('year 0 is out of range'),)
(ValueError('month must be in 1..12'),)
(ValueError('day is out of range for month'),)

当处理中的时间段时pandas

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用