“Ask forgiveness not permission” - explain
我不是在问关于这一哲学的个人"宗教"观点,而是一些更技术的东西。
我理解这个短语是几个石蕊测试之一,看看你的代码是否是"Python"。但对我来说,Python意味着干净、简单和直观,而不是为糟糕的编码加载异常处理程序。
所以,举个实际的例子。我定义了一个类:
1 2 3 4 5 6 7 | 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 |
现在,从程序背景来看,在另一个函数中,我想这样做:
1 2 | if foo.bar: # do stuff |
如果我不耐烦并且没有执行初始foo=none,我将得到一个属性异常。所以,"请求原谅而不是允许"建议我应该这样做?
1 2 3 4 5 | try: if foo.bar: # do stuff except: # this runs because my other code was sloppy? |
为什么我最好在一个try块中添加额外的逻辑,这样我就可以使类定义更加模糊?为什么不先定义所有内容,然后明确地授予权限?
(不要因为使用Try/Except块而痛扁我…我到处都用。我只是觉得用它们来捕捉我自己的错误是不对的,因为我不是一个彻底的程序员。)
或者…我是否完全误解了"请原谅"这句咒语?
"请求原谅,而不是许可"反对两种编程风格。"请求许可"如下:
1 2 3 4 | if can_do_operation(): perform_operation() else: handle_error_case() |
"请求宽恕"是这样的:
1 2 3 4 | try: perform_operation() except Unable_to_perform: handle_error_case() |
在这种情况下,预期尝试执行操作可能会失败,您必须以某种方式处理操作不可能的情况。例如,如果操作正在访问文件,则该文件可能不存在。
请求宽恕有两个主要原因:
- 在同一个世界中(在多线程程序中,或者如果操作涉及到程序外部的对象,如文件、其他进程、网络资源等),运行EDOCX1时(0)和运行EDOCX1时(1)之间的情况可能会发生变化。所以无论如何,你必须处理这个错误。
- 你需要使用正确的标准来请求许可。如果你弄错了,你要么无法执行你可以执行的操作,要么因为你根本无法执行操作而出错。例如,如果在打开文件之前测试该文件是否存在,则可能该文件确实存在,但由于您没有权限,因此无法打开该文件。相反,文件可能是在打开时创建的(例如,因为它通过网络连接,而网络连接仅在实际打开文件时才会打开,而不是仅在插入查看文件是否存在时打开)。
请求宽恕的情况有一个共同点,那就是你试图执行一个操作,你知道这个操作可能会失败。
当你写
1 2 3 4 | if foo.bar is not None: handle_optional_part(foo.bar) else: default_handling() |
只有当
此时,您可能会问:为什么不在不存在时省略
- 对象没有
bar 字段; - 对象有一个
bar 字段,表示您认为它的含义。
因此,在编写或决定使用对象时,您需要确保它没有具有不同含义的
经典的"请求宽恕而非许可"例子是从可能不存在的
1 2 3 4 5 6 7 | 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) |
这里提到了可能发生的例外(
1 2 3 4 | if name in names: print names[name] else: print"Sorry, don't know this '{}' person".format(name) |
或
1 2 3 4 5 | real_name = names.get(name, None) if real_name: print real_name else: print"Sorry, don't know this '{}' person".format(name) |
这种"请求宽恕"的例子往往过于简单。在我看来,不清楚的是,
有关代码示例的其他注释:
您不需要在每个变量使用周围添加
顺便说一下,
你说得对——
异常处理应用于处理异常情况(草率编码不是异常情况)。然而,通常很容易预测会发生哪些异常情况。(例如,您的程序接受用户输入并使用它访问字典,但用户输入不是字典中的键…)
这里有很多好的答案,我只是想补充一点,我到目前为止还没有提到。
通常请求原谅而不是许可会提高性能。
- 当你请求许可时,你必须执行一个额外的操作每次请求许可。
- 请求宽恕时,有时你只需要做一个额外的手术,即它失败了。
通常,失败案例很少,这意味着如果你只是请求许可,那么你几乎不需要做任何额外的操作。是的,当它失败时,它抛出一个异常,并执行一个额外的操作,但是Python中的异常非常快。您可以在这里看到一些时间安排:https://jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/
虽然已经有许多高质量的答案,但大多数主要是从一个风格的角度来讨论这个问题,因为这与一个功能性的答案是一致的。
在某些情况下,我们需要请求原谅,而不是允许确保正确的代码(在多线程程序之外)。
一个典型的例子是,
1 2 | if file_exists: open_it() |
在本例中,文件可能在检查和尝试实际打开文件之间被删除。这可以通过使用
1 2 3 4 | try: open_it() except FileNotFoundException: pass # file doesn't exist |
它出现在许多地方,通常使用文件系统或外部API。
在Python上下文中,"请求原谅而不是许可"意味着一种编程风格,在这种风格中,您不检查事情是否如预期的那样,而是处理错误(如果不是这样的话)。经典的例子是不检查字典是否包含给定的键,如:
1 2 3 4 5 6 | d = {} k ="k" if k in d.keys(): print d[k] else: print"key "" + k +"" missing" |
但是,如果缺少键,则处理所产生的错误:
1 2 3 4 5 6 | d = {} k ="k" try: print d[k] except KeyError: print"key "" + k +"" missing" |
然而,重点并不是用
我个人的非宗教观点是,这句咒语主要适用于记录在案且被充分理解的退出条件和边缘案件(如I/O错误),决不应被用作粗心编程的越狱卡。
也就是说,当存在"更好"的替代方案时,通常使用
1 2 3 4 5 6 7 8 | # Do this value = my_dict.get("key", None) # instead of this try: value = my_dict["key"] except KeyError: value = None |
对于您的示例,如果您无法控制
请求原谅,而不是许可,是为了简化代码。这意味着当有一个合理的期望
1 2 3 4 | try: print foo.bar except AttributeError as e print"No Foo!" |
你的代码似乎既请求许可又请求宽恕:)
问题是,如果您合理地期望某件事情失败,请使用一个尝试/捕获。如果您不希望某个东西失败,而且无论如何失败了,那么抛出的异常就相当于其他语言中失败的断言。您可以看到意外异常发生的位置,并相应地调整代码/假设。