我不是要求对这种哲学提出个人的“宗教”观点,而是要更具技术性。
我理解这句话是几个试金石中的一个,看你的代码是否是“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块......我在任何地方都使用它们。我不认为用它们来捕捉我自己的错误是正确的,因为我不是一个彻底的程序员。)
或者......我是否完全误解了“请求原谅”的口头禅?
答案 0 :(得分:55)
“请求宽恕,而不是许可”反对两种编程风格。 “请求许可”是这样的:
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
的失败。它通常是程序员错误:尝试以不是为其设计的方式使用对象。 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 None
和True
之间进行测试),它也会更清晰。
此时您可能会问:为什么不在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)
你是对的 - try
和except
的目的不是为了掩盖你的草率编码。这只会导致编码更加草率。
应该使用异常处理来处理特殊情况(草率编码不是特殊情况)。但是,通常很容易预测可能发生的特殊情况。 (例如,您的程序接受用户输入并使用它来访问字典,但用户的输入不是字典中的键...)
答案 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。如果你不期望某些东西失败,并且它无论如何都会失败,那么被抛出的异常就等同于其他语言中失败的断言。您可以看到发生意外异常的位置,并相应地调整您的代码/假设。