首页 文章

为什么“除了:通过”一个糟糕的编程习惯?

提问于
浏览 1494
265

我经常看到有关如何不鼓励使用 except: pass 的其他Stack Overflow问题的评论 . 为什么这么糟糕?有时我只是不关心错误是什么,我想继续使用代码 .

try:
    something
except:
    pass

为什么使用 except: pass 块不好?是什么让它变坏?是_ pass 出错还是我 except 任何错误?

16 回答

  • 283

    在我看来,错误有理由出现,我的声音很愚蠢,但就是这样 . 良好的编程只会在你必须处理错误时引发错误 . 另外,正如我前段时间读到的那样,“pass-Statement是一个声明,显示代码将在以后插入”,所以如果你想要一个空的except语句随意这样做,但是对于一个好的程序,失踪的一部分 . 因为你没有处理你应该拥有的东西 . 出现异常使您有机会更正输入数据或更改数据结构,以便不再发生这些异常(但在大多数情况下(网络异常,常规输入异常)异常表示程序的下一部分不会很好地执行 . 例如,NetworkException可以指示网络连接中断,并且程序无法在下一个程序步骤中发送/接收数据 .

    但是只使用一个execption-block的传递块是有效的,因为你仍然在异常类型之间区分,所以如果你将所有异常块放在一个异常块中,它就不是空的:

    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块也可能导致代码,其结构处理特殊类型的异常 .

  • 256

    简单地说,如果抛出异常或错误,那就错了 . 它可能不是非常错误,但仅仅为了使用goto语句而创建,抛出和捕获错误和异常并不是一个好主意,而且很少这样做 . 99%的时间,某处出现了问题 .

    需要解决的问题 . 就像在生活中一样,在编程中,如果你只是单独留下问题并试图忽略它们,它们不仅会自行消失很多次;相反,他们变得越来越大 . 为了防止问题在你身上蔓延并再次在路上再次打击,你要么1)消除它并在之后清理乱七八糟,要么2)包含它并随后清理乱七八糟 .

    只是忽略异常和错误,让它们像这样是一种体验内存泄漏,出色的数据库连接,不必要的文件权限锁定等的好方法 .

    在极少数情况下,问题是如此微不足道,琐碎,并且 - 除了需要尝试...捕获块 - self-contained ,之后确实没有任何混乱被清理 . 这是最佳实践不一定适用的唯一场合 . 根据我的经验,这通常意味着无论代码在做什么都基本上是小事和可靠的,重试尝试或特殊消息之类的东西既不复杂也不值得坚持 .

    在我的公司,规则是几乎总是在catch块中做一些事情,如果你什么都不做,那么你必须总是发表评论,其中有很好的理由 . 当有任何事情要做时,你绝不能传递或留下空的挡块 .

  • 70

    字面上执行伪代码甚至不会出现任何错误:

    try:
        something
    except:
        pass
    

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

  • 45

    处理错误在编程中非常重要 . 您需要向用户显示出错的地方 . 在极少数情况下,您可以忽略错误 . 这是非常糟糕的编程习惯 .

  • 26

    因为它没有't been mentioned yet, it'更好的样式使用contextlib.suppress

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

    请注意,在提供的示例中,无论是否发生异常,程序状态都保持不变 . 也就是说, somefile.tmp 总是不存在 .

  • 23

    那么,这段代码产生了什么输出?

    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 块是对复杂对象层次结构的数百行调用,并且本身在大型程序的调用树中调用 . 当程序出错时,你从哪里开始看?

  • 12

    您应该至少使用 except Exception: 来避免捕获系统异常,如 SystemExitKeyboardInterrupt . 这是link到docs .

    通常,您应该明确定义要捕获的异常,以避免捕获不需要的异常 . 你应该知道你忽略了什么例外 .

  • 11

    首先,它违反了Zen of Python的两个原则:

    • Explicit is better than implicit

    • Errors should never pass silently

    这意味着,你是故意让你的错误无声地通过 . 此外,您没有事件知道,确实发生了哪个错误,因为 except: pass 将捕获任何异常 .

    其次,如果我们试图从Python的禅宗中抽象出来,并且仅仅说出理智,那么你应该知道,使用 except:pass 会在你的系统中留下 no knowledge and control . 经验法则是如果发生错误则引发异常,并采取适当的措施 . 如果你事先不知道,应该采取什么行动,至少在某处记录错误(最好重新提出异常):

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

    但是,通常, if you try to catch any exception, you are probably doing something wrong!

  • 11

    except:pass 构造基本上使在运行 try: 块中的代码时出现的任何和所有异常条件都无声 .

    What makes this bad practice is that it usually isn't what you really want. 更常见的是,某种特定的情况即将出现,你想要沉默,而且 except:pass 太过钝了 . 它将完成工作,但它也会掩盖您可能没有预料到的其他错误情况,但可能非常希望以其他方式处理 .

    这在Python中特别重要的是这种语言的习语, exceptions are not necessarily errors . 它们确实是正常运行情况的一部分,但仍然会不时出现,在大多数情况下甚至可能会出现这种情况 . SystemExit 已经被提及作为一个旧的例子,但现在最常见的例子可能是 StopIteration . 以这种方式使用异常引起了很多争议,特别是当迭代器和生成器首次引入Python时,但最终这个想法占了上风 .

  • 11

    通常,您可以在three categories之一中对任何错误/异常进行分类:

    • Fatal :不是你的错,你不能阻止它们,你无法从中恢复 . 您当然不应该忽略它们并继续,并使您的程序处于未知状态 . 只是让错误终止你的程序,没有什么可以做 .

    • Boneheaded :您自己的错,很可能是由于疏忽,错误或编程错误造成的 . 你应该修复这个bug . 同样,你绝对不应该忽视并继续 .

    • Exogenous :在特殊情况下可能会出现这些错误,例如找不到文件或终止连接 . 你应该明确地处理这些错误,只有这些错误 .

    在所有情况下, except: pass 只会使您的程序处于未知状态,从而导致更多损坏 .

  • 10

    这里的主要问题是它忽略了所有错误:内存不足,CPU正在烧毁,用户想要停止,程序要退出,Jabberwocky正在查杀用户 .

    这太过分了 . 在你的脑海里,你在想"I want to ignore this network error" . 如果出现意外情况,那么您的代码将以静默方式继续,并以完全不可预测的方式中断,无人可以调试 .

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

  • 5

    为什么“除了:通过”一个糟糕的编程习惯?为什么这么糟糕?尝试:
    某物
    除了:
    通过

    这会捕获每个可能的异常,包括 GeneratorExitKeyboardInterruptSystemExit - 这些异常可能与捕获 BaseException 相同 .

    try:
        something
    except BaseException:
        pass
    

    documentation sayOlder版本:

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

    Python异常层次结构

    如果捕获父异常类,则还会捕获所有子类 . 只捕获您准备处理的异常会更加优雅 .

    这是Python 3 exception hierarchy - 你真的想要 grab 所有人吗?:

    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
    

    然后,您将无法使用Ctrl-C中断 something 块 . 您的程序将忽略 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

    #1的原因已经被陈述 - 它隐藏了你没想到的错误 .

    (#2) - It makes your code difficult for others to read and understand. 如果在尝试读取文件时捕获到FileNotFoundException,那么对于另一个开发人员来说,'catch'块应该具有哪些功能 . 如果您没有指定异常,那么您需要额外的注释来解释该块应该做什么 .

    (#3) - It demonstrates lazy programming. 如果使用通用try / catch,则表示您不了解程序中可能存在的运行时错误,或者您不知道Python中可能存在哪些异常 . 捕获特定错误表明您了解程序和Python抛出的错误范围 . 这更有可能使其他开发人员和代码审阅者信任您的工作 .

  • 4

    正如您所猜测的那样,它有两个方面:通过在 except 之后指定无异常类型来捕获任何错误,并且只是在不采取任何操作的情况下传递它 .

    我的解释是“有点”更长 - 所以tl; dr它分解为:

    • Don’t catch any error . 始终指定您准备从哪些例外中恢复并仅捕获这些例外 .

    • Try to avoid passing in except blocks . 除非明确要求,否则这通常不是一个好兆头 .

    但是让我们详细说明:

    不要发现任何错误

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

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

    但是,如果我们要 grab 所有东西,那么 - 除了那些我们准备从中恢复的例外 - 我们也有机会获得我们没想到的异常,而且我们确实无法从中恢复;或者不应该从中恢复 .

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

    Python 2 idioms文件中也提到了另一个简单的例子 . 这里,代码中存在一个简单的拼写错误,导致它破坏 . 因为我们正在捕获每个异常,所以我们也会捕获NameErrorsSyntaxErrors . 两者都是编程时发生在我们身上的错误;并且两者都是我们在发货时绝对不想包含的错误 . 但是因为我们也 grab 了这些,我们甚至不知道它们发生在那里并且失去了正确调试它的任何帮助 .

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

    在任何情况下,您都不太可能为代码的小规模部分中的所有内容做好准备,因此您应该只捕获您准备好的异常 . 有些人建议至少要 grab Exception,因为它不包括 SystemExitKeyboardInterrupt 之类的东西,它们的设计是为了终止你的应用程序,但我认为这仍然太不明确了 . 只有一个地方,我个人接受捕获 Exception 或只是任何异常,这是在一个单一的全局应用程序级异常处理程序,其唯一目的是记录我们没有准备的任何异常 . 这样,我们仍然可以保留关于意外异常的尽可能多的信息,然后我们可以使用它来扩展我们的代码以明确处理它们(如果我们可以从它们中恢复)或者 - 如果出现错误 - 创建测试用例以确保它不会再发生 . 但当然,那只是如果我们只 grab 了我们已经预料到的那些例外,那么我们没想到的那些例子会自然而然地冒出来 .

    尽量避免传入除块之外的传递

    当明确地捕获一小部分特定异常时,在很多情况下我们可以通过简单地无所事事来完成 . 在这种情况下,只需要 except SomeSpecificException: pass 就可以了 . 但大多数时候情况并非如此,因为我们可能需要一些与恢复过程相关的代码(如上所述) . 例如,这可以是再次重试动作,或者设置默认值 .

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

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

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

    在许多其他情况下,只是传入一个 except 表明我们并没有为我们捕获的异常做好准备 . 除非这些例外很简单(例如 ValueErrorTypeError ),并且我们可以传递的原因很明显,请尽量避免传递 . 如果真的没什么可做的(而且你绝对肯定),那么考虑添加评论为什么会这样;否则,展开except块以实际包含一些恢复代码 .

    除外:通过

    最糟糕的罪犯是两者的结合 . 这意味着我们愿意 grab 任何错误,尽管我们绝对没有为此做好准备 and 我们也没有采取任何措施 . 您至少想要记录错误并且还可能重新加载它仍然终止应用程序(在MemoryError之后,您不太可能像正常一样继续) . 通过虽然不仅会使应用程序保持活跃(取决于你当然捕获的地方),但也会丢弃所有信息,使得无法发现错误 - 如果你不是发现它的话,尤其如此 .


    所以底线是:只 grab 你真正期望的并准备从中恢复的例外;所有其他可能是你应该修复的错误,或者你不准备的事情 . 如果你真的不需要对它们做些什么,那么传递特定的例外是好的 . 在所有其他情况下,这只是推定和懒惰的标志 . 你肯定想解决这个问题 .

  • 2
    >>> import this
    

    蒂姆·彼得斯的“禅之谜”美丽胜过丑陋 . 显式优于隐式 . 简单比复杂更好 . 复杂比复杂更好 . Flat优于嵌套 . 稀疏优于密集 . 可读性很重要 . 特殊情况不足以打破规则 . 虽然实用性胜过纯洁 . 错误不应该默默地传递 . 除非明确沉默 . 面对模棱两可,拒绝猜测的诱惑 . 应该有一个 - 最好只有一个 - 显而易见的方法 . 虽然这种方式起初可能并不明显,除非你是荷兰人 . 现在总比没有好 . 虽然从来没有比现在更好 . 如果实施很难解释,那是个坏主意 . 如果实现很容易解释,那可能是个好主意 . 命名空间是一个很棒的主意 - 让我们做更多的事情吧!

    所以,这是我的意见 . 每当您发现错误时,您应该做一些事情来处理它,即将其写入日志文件或其他内容 . 至少,它告诉你曾经有过错误 .

  • 0

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

    尽管如此,不要把任何编程习惯作为最重要的 . 这是愚蠢的 . 总是有时间和地点做ignore-all-exceptions块 .

    愚蠢至上的另一个例子是使用 goto 运算符 . 当我在学校的时候,我们的教授教我们 goto 操作员只是提到你不能使用它,永远 . 不要相信人们告诉你xyz永远不应该被使用,并且当它有用时就不会出现这种情况 . 总有 .

相关问题