86

我不是在要求关于这种哲学的个人“宗教”观点,而是更多技术性的观点。

我知道这句话是几个试金石之一,看看你的代码是否是“pythonic”。但对我来说,pythonic 意味着干净、简单和直观,没有加载用于糟糕编码的异常处理程序。

所以,实际的例子。我定义了一个类:

class foo(object):
    bar = None

    def __init__(self):
        # a million lines of code
        self.bar = "Spike is my favorite vampire."
        # a million more lines of code

现在,来自程序背景,在另一个功能中我想这样做:

if foo.bar:
    # do stuff

如果我不耐烦并且没有执行初始的 foo = None,我会得到一个属性异常。所以,“请求宽恕而不是许可”建议我应该这样做吗?

try:
    if foo.bar:
        # do stuff
except:
    # this runs because my other code was sloppy?

为什么我最好在 try 块中添加额外的逻辑,这样我就可以让我的类定义更加模糊?为什么不首先定义所有内容,然后明确授予权限

(不要因为使用 try/except 块而责备我……我在任何地方都使用它们。我只是不认为使用它们来捕获我自己的错误是正确的,因为我不是一个彻底的程序员。)

或者……我完全误解了“请求原谅”的咒语吗?

4

8 回答 8

82

“请求宽恕,而不是许可”反对两种编程风格。“请求许可”是这样的:

if can_do_operation():
    perform_operation()
else:
    handle_error_case()

“请求原谅”是这样的:

try:
    perform_operation()
except Unable_to_perform:
    handle_error_case()

在这种情况下,预计尝试执行操作可能会失败,您必须以一种或另一种方式处理无法执行操作的情况。例如,如果操作正在访问文件,则该文件可能不存在。

请求宽恕的原因主要有两个:

  • 在并发世界中(在多线程程序中,或者如果操作涉及程序外部的对象,例如文件、其他进程、网络资源等),情况可能会在运行can_do_operation()时间和运行时间之间发生变化。你跑perform_operation()。所以无论如何你都必须处理错误。
  • 您需要使用完全正确的标准来请求许可。如果您弄错了,您将无法执行您可以执行的操作,或者因为您根本无法执行该操作而发生错误。例如,如果您在打开文件之前测试它是否存在,则可能该文件确实存在,但您无法打开它,因为您没有权限。相反,也许该文件是在您打开它时创建的(例如,因为它来自一个仅在您实际打开文件时才出现的网络连接,而不是当您只戳看它是否存在时)。

请求宽恕情况的共同点是您正在尝试执行一项操作,并且您知道该操作可能会失败。

当你写foo.bar的时候,不存在bar通常不会被认为是对象的失败foo。这通常是程序员的错误:试图以非设计的方式使用对象。Python 中程序员错误的后果是未处理的异常(如果幸运的话:当然,一些程序员错误无法自动检测到)。所以 ifbar是对象的一个​​可选部分,处理这个问题的正常方法是有一个bar初始化为 的字段,None如果可选部分存在,则设置为其他值。要测试是否bar存在,请编写

if foo.bar is not None:
     handle_optional_part(foo.bar)
else:
     default_handling()

当解释为布尔值时,您可以缩写if foo.bar is not None:if foo.bar:only if will always be true — 如果可以是 0 、或任何其他具有错误真值的对象,则需要. 如果您正在测试可选部分(而不是在 and 之间进行测试),这也更清楚。barbar[]{}is not NoneTrueFalse

此时你可能会问:为什么不省略初始化bar时它不存在,并用处理程序测试它的存在hasattr或捕获它AttributeError?因为您的代码仅在两种情况下才有意义:

  • 该对象没有bar场;
  • 该对象有一个bar字段,表示您认为它的含义。

因此在编写或决定使用该对象时,您需要确保它没有具有bar不同含义的字段。如果您需要使用一些没有bar字段的不同对象,那可能不是您需要适应的唯一事情,因此您可能希望创建一个派生类或将对象封装在另一个类中。

于 2012-09-04T19:49:28.693 回答
80

经典的“请求宽恕而不是许可”示例是访问dict可能不存在的值。例如:

names = { 'joe': 'Joe Nathan', 'jo': 'Jo Mama', 'joy': 'Joy Full' }
name = 'hikaru'

try:
    print names[name]
except KeyError:
    print "Sorry, don't know this '{}' person".format(name)

此处说明了可能发生的异常 ( KeyError),因此您不会为可能发生的每个错误请求宽恕,而只是针对自然发生的错误请求宽恕。相比之下,“先请求许可”的方法可能如下所示:

if name in names:
    print names[name]
else:
    print "Sorry, don't know this '{}' person".format(name)

或者

real_name = names.get(name, None)
if real_name:
    print real_name
else:
    print "Sorry, don't know this '{}' person".format(name)

这种“请求原谅”的例子往往过于简单。IMO 不清楚try/except块本质上优于if/ else。当执行可能以各种方式失败的操作时,真正的价值要清晰得多——比如解析;使用eval();访问操作系统、中间件、数据库或网络资源;或执行复杂的数学。当存在多种潜在的失败模式时,准备好获得宽恕是非常有价值的。

关于您的代码示例的其他说明:

您不需要在每个变量使用情况周围盛装try/阻塞。except那将是可怕的。而且你不需要设置self.bar你的,__init__()因为它是在你class上面的定义中设置的。通常在类中定义它(如果它的数据可能在类的所有实例之间共享)或在__init__()(如果它是实例数据,特定于每个实例)。

顺便说一句,的值None不是未定义的,也不是错误的。它是一个特定且合法的值,表示 none、nil、null 或无。许多语言都有这样的值,因此程序员不会“重载” 0, -1, ''(空字符串)或类似的有用值。

于 2012-09-04T14:40:16.803 回答
19

这里有很多很好的答案,我只是想我会添加一个到目前为止我还没有提到的观点。

通常请求宽恕而不是许可可以提高性能。

  • 当您请求许可时,您必须执行额外的操作以每次请求许可。
  • 当请求宽恕时,您只需要在某些时候执行额外的操作,即当它失败时。

通常失败的情况很少见,这意味着如果你只是请求许可,那么你几乎不需要做任何额外的操作。是的,当它失败时,它会抛出异常,并执行额外的操作,但是 python 中的异常非常快。你可以在这里看到一些时间:https ://jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/

于 2017-07-26T03:33:28.963 回答
8

你是对的——目的try不是except为了掩盖你草率的编码。这只会导致更草率的编码。

异常处理应该用于处理异常情况(马虎编码不是异常情况)。但是,通常很容易预测可能会发生哪些异常情况。(例如,您的程序接受用户输入并使用它来访问字典,但用户的输入不是字典中的键......)

于 2012-09-04T14:21:50.600 回答
7

我个人的非宗教观点是,所说的口头禅主要适用于记录在案易于理解的退出条件和边缘情况(例如 I/O 错误),并且永远不应该用作草率编程的越狱卡。

也就是说,try/except通常在存在“更好”的替代方案时使用。例如:

# Do this    
value = my_dict.get("key", None)

# instead of this
try:
  value = my_dict["key"]
except KeyError:
  value = None

至于您的示例,if hasattr(foo, "bar")如果您无法控制foo并需要检查是否符合您的期望,请使用,否则只需使用foo.bar并让结果错误成为您识别和修复草率代码的指南。

于 2012-09-04T14:28:09.450 回答
6

虽然已经有许多高质量的答案,但主要是从风格的角度来讨论这个问题,与功能性的观点相对应。

在某些情况下,我们需要请求宽恕,而不是允许确保正确的代码(在多线程程序之外)。

一个典型的例子是,

if file_exists: 
    open_it()

在此示例中,文件可能已在检查和尝试实际打开文件之间被删除。这可以通过使用来避免try

try:
    open_it()
except FileNotFoundException:
    pass # file doesn't exist 

这出现在很多地方,通常与文件系统或外部 API 一起使用。

于 2018-01-05T02:19:21.050 回答
5

在 Python 上下文中,“请求宽恕而不是许可”意味着一种编程风格,在这种风格中,您不会事先检查事情是否符合您的预期,而是处理如果错误导致的错误。经典示例不是检查字典是否包含给定键,如下所示:

d = {}
k = "k"
if k in d.keys():
  print d[k]
else:
  print "key \"" + k + "\" missing"

而是在缺少密钥时处理由此产生的错误:

d = {}
k = "k"
try:
  print d[k]
except KeyError:
  print "key \"" + k + "\" missing"

但是,重点不是用/替换if代码中的every ;这会让你的代码更加混乱。相反,您应该只捕获真正可以对它们做些什么的错误。理想情况下,这将减少代码中的整体错误处理量,使其实际目的更加明显。tryexcept

于 2012-09-04T14:38:55.087 回答
2

请求宽恕而不是许可旨在简化代码。.bar当存在可能触发 AttributeError的合理预期时,它意味着要像这样编写代码。

 try:
     print foo.bar
 except AttributeError as e
     print "No Foo!" 

您的代码似乎要求许可和宽恕:)

问题是,如果您有理由预期某些事情会失败,请使用 try/catch。如果您不期望某些事情会失败,并且无论如何都会失败,那么抛出的异常就相当于其他语言中的失败断言。您会看到发生意外异常的位置并相应地调整您的代码/假设。

于 2012-09-04T14:22:14.883 回答