python isinstance vs hasattr vs try/except: 什么更好?

26

我试图弄清楚在确定是否可以执行操作 do_stuff() 的对象obj的不同方法之间的权衡。据我所知,有三种确定可行性的方法:

# 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'

哪种方法更受青睐(为什么)?


2
每件事都有其时。通常情况下,使用try/except(或者甚至不要使用except,只需让异常沿着调用堆栈向上传递)是最好的方法,但是每个规则都有例外(hohoho)。例如,字符串和列表共享许多方法,但在递归调用中,您经常希望对它们执行不同的操作。 - roippi
3个回答

15

我相信Python程序员通常更喜欢最后一种方法,因为在Python社区中有一个motto:“宁愿请求原谅,也不要事先获准”(EAFP)。

简而言之,这个座右铭意味着在操作之前避免检查是否可以执行该操作。相反,只需运行操作。如果失败了,则适当处理它。

此外,第三种方法还具有使操作应该正常工作的明显优势。


说到这里,你真的应该避免像那样使用裸的except。这样做会捕获任何/所有异常,甚至是无关的异常。相反,最好专门捕获异常。

在这里,你将想要捕获一个 AttributeError:

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

尝试在else子句中访问方法属性并调用它,而不是仅在try子句中尝试调用它,是否有任何好处?(我想你可能想区分在do_stuff内部引发的AttributeError和由于do_stuff不存在而生成的错误。) - chepner
@chepner - 不,你发现得很好。我想的是另一件事情。 - user2555451

8

使用 isinstance 进行检查与 Python 使用 鸭子类型 的惯例相违背。

hasattr 可以正常工作,但它是 先行检查后继续操作 ,而不是更 Pythonic 的 先做再容错

第三种方式的实现很危险,因为它捕获了所有错误,包括由 do_stuff 方法引发的错误。你可以选择更加精确的实现:

try:
    _ds = obj.do_stuff
except AttributeError:
    print('Do something else')
else:
    _ds()

但在这种情况下,我更喜欢方式2,尽管会稍微增加一些负担——因为这样更易读。


我猜你的意思是“hasattr运行良好,但是采用了EAFP而不是更Pythonic的LBYL”,而实际上应该是相反的,“hasattr运行良好,但是采用了LBYL而不是更Pythonic的EAFP”。 - mloskot

5
正确答案是“都不是”。 hasattr提供了功能,但可能是最糟糕的选择之一。我们使用Python的面向对象特性,因为它有效。面向对象分析永远不准确,经常会让人困惑,但我们使用类层次结构,因为我们知道它们可以帮助人们更快地完成更好的工作。人们可以理解对象,良好的对象模型有助于编码人员更快地进行更少的错误更改。正确的代码最终聚集在正确的位置。对象:
- 可以在不考虑哪个实现存在的情况下使用 - 明确需要更改的内容和位置 - 从某些其他功能的更改中隔离出来-您可以修复X而不担心会破坏Y
hasattr vs isinstance 必须使用isinstance或hasattr表示对象模型已损坏或我们正在错误地使用它。正确的做法是修复对象模型或更改我们使用它的方式。这两个构造具有相同的效果,在命令式的“我需要代码来做这件事”的意义上它们是等价的。在结构上有很大的区别。第一次遇到此方法(或做其他事情的几个月后),isinstance会传达有关实际发生的事情和其他可能性的更多信息。hasattr什么也不会告诉你。
长期的开发历史使我们远离FORTRAN和带有大量“我是谁”的开关代码。我们选择使用对象,因为我们知道它们有助于使代码更易于使用。通过选择hasattr,我们提供了功能,但什么都没有固定,代码比我们开始时更加糟糕。在将来添加或更改此功能时,我们将不得不处理不均匀分组的代码,并且至少有两个组织原则,其中一些代码位于“应该在”的位置,其余代码随机散布在其他地方。没有任何东西可以使它凝聚。这不是一个错误,而是一个潜在错误的雷区,散布在通过您的hasattr的任何执行路径上。
因此,如果有任何选择,顺序如下:
- 使用对象模型或修复它或至少找出问题所在以及如何修复它 - 使用isinstance - 不要使用hasattr

1
在这种情况下,"修复对象模型"是什么意思?此外,在这里使用"isinstance"有什么好处?您可能想检查例如"object"是否具有形状属性的数据结构,而必须预先列出所有要传递给"isinstance"的结构将是一个问题。最后,您认为一些人认为的"getattr"和"try/except"是好的解决方案吗? - Ando Jurai
@ando jurai 很好的问题,我认为我可以举个例子,如果这个问题不是一个修辞性提示来改善回答。我正在审查一个PR,其中hasattr检查某个数学参数以确定要解决的问题(类)的类型。hasattr包括一行注释,解释作者正在寻找的类,使用isinstance显然会更明显。修复数据模型通常意味着使用间接性。例如,如果代码期望类FooBar,则向Bar添加一个只运行passdo_stuff()方法就可以避免任何控制流的需要。 - Jake Stevens-Haas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接