“请求原谅不许可” - 解释

时间:2012-09-04 14:18:19

标签: python

我不是要求对这种哲学提出个人的“宗教”观点,而是要更具技术性。

我理解这句话是几个试金石中的一个,看你的代码是否是“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块......我在任何地方都使用它们。我不认为用它们来捕捉我自己的错误是正确的,因为我不是一个彻底的程序员。)

或者......我是否完全误解了“请求原谅”的口头禅?

8 个答案:

答案 0 :(得分:55)

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

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

“请求宽恕”是这样的:

try:
    perform_operation()
except Unable_to_perform:
    handle_error_case()

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

为什么要请求宽恕最好有两个主要原因:

  • 在一个复杂的世界中(在多线程程序中,或者如果操作涉及程序外部的对象,如文件,其他进程,网络资源等),情况可能会在您运行的时间之间发生变化{ {1}}以及您运行can_do_operation()的时间。因此,无论如何你都必须处理错误。
  • 您需要使用完全正确的标准来询问权限。如果你弄错了,你要么无法执行你可以执行的操作,要么发生错误,因为你毕竟无法执行操作。例如,如果在打开文件之前测试文件是否存在,则文件可能存在,但由于您没有权限,因此无法打开文件。相反,也许文件是在打开文件时创建的(例如,因为它来自网络连接,只有在您实际打开文件时才会显示,而不是当您只是查看它是否存在时)。

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

当您编写perform_operation()时,foo.bar的不存在通常不会被视为对象bar的失败。它通常是程序员错误:尝试以不是为其设计的方式使用对象。 Python中程序员错误的结果是未处理的异常(如果你很幸运:当然,有些程序员错误无法自动检测到)。因此,如果foo是对象的可选部分,则处理此问题的常规方法是将bar字段初始化为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: - 如果bar可以为0,bar[]或任何其他具有虚假真值的对象,您需要{}。如果您正在测试可选部分(而不是在is not NoneTrue之间进行测试),它也会更清晰。

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

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

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

答案 1 :(得分:50)

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

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的值未定义或错误。它是一个特定的合法值,意思是无,零,无或无。许多语言都有这样的值,因此程序员不会“重载”0-1''(空字符串)或类似的有用值。

答案 2 :(得分:7)

你是对的 - tryexcept的目的不是为了掩盖你的草率编码。这只会导致编码更加草率。

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

答案 3 :(得分:6)

这里有很多好的答案,我只是想加上我迄今未提及的一点。

通常要求宽恕而非许可会提高性能。

  • 当您要求许可时,您必须执行额外的操作 要求获得每个时间的许可。
  • 在请求宽恕时, 你只需要执行额外的操作有时,即何时 它失败了。

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

答案 4 :(得分:5)

我个人的非宗教观点是,所述口头禅主要适用于记录的很好理解的退出条件和边缘情况(例如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并将结果错误发送给您识别和修复草率代码的指南。

答案 5 :(得分:4)

在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 / try替换代码中的每个except;这会让你的代码变得更加混乱。相反,你应该只捕捉你真正可以对它们做些什么的错误。理想情况下,这将减少代码中的整体错误处理量,使其实际目的更加明显。

答案 6 :(得分:4)

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

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

一个典型的例子是,

if file_exists: 
    open_it()

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

可以避免这种情况
try:
    open_it()
except FileNotFoundException:
    pass # file doesn't exist 

这种情况出现在各种各样的地方,通常使用文件系统或外部API。

答案 7 :(得分:1)

请求宽恕不是权限是为了简化代码。当合理期望.bar可能触发AttributeError时,这意味着要像这样编写代码。

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

您的代码似乎同时请求许可和宽恕:)

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