"请求原谅不许可" - 解释

hik*_*aru 58 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
Run Code Online (Sandbox Code Playgroud)

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

if foo.bar:
    # do stuff
Run Code Online (Sandbox Code Playgroud)

如果我不耐烦并且没有做初始的foo = None,我会得到一个属性异常.所以,"请求宽恕不许可"表明我应该这样做吗?

try:
    if foo.bar:
        # do stuff
except:
    # this runs because my other code was sloppy?
Run Code Online (Sandbox Code Playgroud)

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

(不要打扰我使用try/except块......我在任何地方都使用它们.我不认为用它们来捕捉我自己的错误是正确的,因为我不是一个彻底的程序员.)

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

Gil*_*il' 59

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

if can_do_operation():
    perform_operation()
else:
    handle_error_case()
Run Code Online (Sandbox Code Playgroud)

"请求宽恕"是这样的:

try:
    perform_operation()
except Unable_to_perform:
    handle_error_case()
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

当你写作时foo.bar,不存在bar通常不被认为是对象的失败foo.它通常是程序员错误:尝试以不是为其设计的方式使用对象.Python中程序员错误的结果是一个未处理的异常(如果你很幸运:当然,有些程序员错误无法自动检测到).因此,如果bar是对象的可选部分,处理此问题的常规方法是使bar字段初始化为None,如果存在可选部分,则设置为其他值.要测试是否bar存在,请写入

if foo.bar is not None:
     handle_optional_part(foo.bar)
else:
     default_handling()
Run Code Online (Sandbox Code Playgroud)

只有在解释为布尔值时才能将其缩写if foo.bar is not None:为真 - 如果可能是0 ,或者任何其他具有错误真值的对象,则需要.如果您正在测试可选部件(而不是在和之间进行测试),那么它也更清晰.if foo.bar:barbar[]{}is not NoneTrueFalse

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

  • 对象没有bar字段;
  • 该对象有一个bar字段,表示您认为它意味着什么.

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


Jon*_*ice 56

经典的"请求宽恕不允许"示例是从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)
Run Code Online (Sandbox Code Playgroud)

这里KeyError列出了可能发生的异常(),因此您不会对可能发生的每个错误请求宽恕,而只会请求自然发生的错误.为了比较,"首先询问权限"方法可能如下所示:

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

要么

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

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

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

您不需要在每个变量用法周围使用钢包try/ except块.那太可怕了.而你并不需要设置self.bar你的__init__(),因为它在你的设置class上面的定义.通常在类中定义它(如果它的数据可能在类的所有实例之间共享)或者在__init__()(如果它是实例数据,特定于每个实例)中定义它.

None顺便说一句,值不是未定义或错误.它是一个特定的合法值,意思是无,零,无或无.许多语言都有这样的值,以便程序员不"超载" 0,-1,''(空字符串)或类似的实用价值.

  • 每个人都有很棒的评论.我将此标记为答案,因为它为我澄清了另外两件事:"通常在类中定义它(如果它的数据可能在类的所有实例之间共享)或在__init __()中定义(如果它是实例数据,特定于每个实例)." 是一个必要的提醒我,在OO编程中如何工作,"当有多种潜在的失败模式时,准备获得宽恕是非常有价值的." 感谢大家的好评,并为我糟糕的例子感到抱歉. (3认同)

mgi*_*son 7

你说得对-的目的try,并except不能覆盖你草率的编码.这只会导致编码更加草率.

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


Jon*_*lie 7

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

通常要求宽恕而不是许可会提高性能.

  • 当您要求许可时,您必须执行额外的操作以便每次都要求许可.
  • 当请求原谅,你只需要进行额外的操作有时,即当它失败.

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


Sha*_*hin 5

我个人的非宗教的看法是,说口头禅主要适用于记录充分理解退出条件和优势的情况下(例如I / O错误),并且不应该被用来作为一个get-外的监狱卡草率的编程。

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

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

# instead of this
try:
  value = my_dict["key"]
except KeyError:
  value = None
Run Code Online (Sandbox Code Playgroud)

就您的示例而言,if hasattr(foo, "bar")如果您无法控制foo并且需要检查是否符合期望,请使用,否则请简单使用foo.bar并让产生的错误作为识别和修复草率代码的指南。


Nic*_*tti 5

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

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

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

d = {}
k = "k"
try:
  print d[k]
except KeyError:
  print "key \"" + k + "\" missing"
Run Code Online (Sandbox Code Playgroud)

然而,重点不是iftry/替换代码中的each except;这将使您的代码明显更加混乱。相反,您应该只在您真正可以对其采取措施的地方捕获错误。理想情况下,这将减少代码中整体错误处理的数量,使其实际目的更加明显。


小智 5

虽然已经有许多高质量的答案,但大多数主要是从文体的角度来讨论这个问题,就像一个功能性的一样。

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

一个典型的例子是,

if file_exists: 
    open_it()
Run Code Online (Sandbox Code Playgroud)

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

try:
    open_it()
except FileNotFoundException:
    pass # file doesn't exist 
Run Code Online (Sandbox Code Playgroud)

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