关于异常处理:python isinstance vs hasattr vs try / except:什么更好?

python isinstance vs hasattr vs try/except: What is better?

我试图找出不同方法之间的权衡,以确定对象obj是否可以执行动作do_stuff()。据我所知,有三种方法可以确定这是否可行:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Way 1
if isinstance(obj, Foo):
    obj.do_stuff()

# Way 2
if hasattr(obj, 'do_stuff'):
    obj.do_stuff()

# Way 3
try:
    obj.do_stuff()
except:
    print 'Do something else'

哪个是首选方法(为什么)?


我相信最后一种方法通常被Python编码人员首选,因为在Python社区中有一句格言:"请求宽恕比请求许可更容易"(EAFP)。

简而言之,这句格言的意思是避免在做之前检查你是否能做些什么。相反,只需运行该操作。如果失败,请妥善处理。

另外,第三种方法还有一个额外的优点,就是可以清楚地表明操作应该有效。

有了这一点,您真的应该避免使用这样的裸except。这样做将捕获任何/所有异常,甚至不相关的异常。相反,最好是专门捕获异常。

在这里,您将要捕获一个AttributeError

1
2
3
4
try:
    obj.do_stuff()   # Try to invoke do_stuff
except AttributeError:
    print 'Do something else'  # If unsuccessful, do something else


使用isinstance进行检查与使用duck类型的python约定背道而驰。

hasattr工作得很好,但它是在你跳跃之前看的,而不是更像是Python的EAFP。

方法3的实现是危险的,因为它捕获任何和所有错误,包括由do_stuff方法引发的错误。你可以更精确地说:

1
2
3
4
5
6
try:
    _ds = obj.do_stuff
except AttributeError:
    print('Do something else')
else:
    _ds()

但在本例中,尽管开销很小,但我还是更喜欢第2种方式——它的可读性更高。


正确答案是"两者都不是"hasattr提供了功能,但它可能是所有选项中最糟糕的。

我们使用Python的面向对象特性,因为它可以工作。OO分析永远不会准确,而且经常混淆,但是我们使用类层次结构,因为我们知道它们可以帮助人们更快地完成更好的工作。人们掌握对象,一个好的对象模型可以帮助编码人员更快地改变事情,减少错误。正确的代码最终聚集在正确的位置。对象:

  • 可以在不考虑存在哪种实现的情况下使用
  • 明确需要改变的地方
  • 将某些功能的更改与某些其他功能的更改隔离开来–您可以修复X,而不必担心会破坏Y

hasattr与isinstance

必须使用isInstance或hasattr表示对象模型已损坏或使用错误。正确的做法是修复对象模型或更改我们使用它的方式。这两个构造具有相同的效果,在命令式"我需要代码来做这件事"中,它们是等效的。结构上有很大的不同。在第一次使用这种方法时(或者在做了几个月的其他事情之后),IsInstance会传递更多关于实际发生的事情以及其他可能发生的事情的信息。哈萨特不会"告诉"你任何事情。

长期的开发历史使我们远离了Fortran,而是使用了大量的"我是谁"开关来编写代码。我们选择使用对象是因为我们知道它们有助于使代码更容易使用。通过选择hasattr,我们提供了功能,但是没有什么可以修复,代码比我们开始之前更容易被破坏。在将来添加或更改此功能时,我们将不得不处理不平等分组且至少有两个组织原则的代码,其中一些原则是"应该"的,其余的则随机分散在其他地方。没有什么可以使它连贯。这不是一个bug,而是散布在通过hasattr的任何执行路径上的潜在错误的雷区。

因此,如果有选择的话,顺序是:

  • 使用对象模型或修复它,或者至少找出问题所在有了它以及如何修复它
  • 使用实例
  • 不要使用hasattr