使用try/catch还是验证来提高速度?
我在用Python编程,每次需要验证函数输入的时候,我通常是先假设输入是正确的,然后再捕捉错误。
在我的例子中,我有一个通用的 Vector()
类,用于处理几种不同的事情,其中之一就是加法。这个类既可以作为 Color()
类使用,也可以作为 Vector()
使用,所以当我把一个数加到 Color()
时,它应该把这个常数加到每个单独的组成部分上。而 Vector()
和 Vector()
的加法则需要逐个组成部分相加。
这段代码是用在光线追踪器里的,所以任何速度上的提升都是很重要的。
下面是我简化版的 Vector()
类:
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __add__(self, other):
try:
return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
except AttributeError:
return Vector(self.x + other, self.y + other, self.z + other)
我现在使用的是 try...except
方法。有没有人知道更快的方法呢?
编辑:感谢大家的回答,我尝试并测试了以下解决方案,它在添加 Vector()
对象之前,专门检查类名:
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __add__(self, other):
if type(self) == type(other):
return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
else:
return Vector(self.x + other, self.y + other, self.z + other)
我用 timeit
进行了这两段代码的速度测试,结果非常明显:
1.0528049469 usec/pass for Try...Except
0.732456922531 usec/pass for If...Else
Ratio (first / second): 1.43736090753
我还没有测试过 Vector()
类完全不进行输入验证的情况(也就是把检查移出类,放到实际代码中),但我想这会比 if...else
方法更快。
后续更新:回头看这段代码,这并不是一个最优的解决方案。
面向对象编程(OOP)让这个过程更快:
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
class Color(Vector):
def __add__(self, other):
if type(self) == type(other):
return Color(self.x + other.x, self.y + other.y, self.z + other.z)
else:
return Color(self.x + other, self.y + other, self.z + other)
2 个回答
在Python中,处理异常通常会更快,因为需要查找的东西比较少。不过,有个朋友曾经说过(这应该适用于任何编程语言),可以想象每次捕获到异常时,都会有一点小延迟。所以,如果你觉得延迟会造成问题,就尽量避免使用异常。
在你给的例子中,我会选择使用异常。
我赞同Matt Joiner的回答,但想补充一些观察,以便更清楚地说明,在选择预先检查条件(称为LBYL或“先看后跳”)和处理异常(称为EAFP或“更容易请求原谅而不是请求许可”)时,有4个时间点是非常重要的。
这四个时间点是:
- 使用LBYL时检查成功的时间
- 使用LBYL时检查失败的时间
- 使用EAFP时没有抛出异常的时间
- 使用EAFP时抛出异常的时间
还有一些额外的因素:
- 检查成功/失败或异常抛出/未抛出的典型比例
- 是否存在竞争条件,导致无法使用LBYL
最后一点需要优先考虑:如果存在潜在的竞争条件,那么你别无选择,必须使用异常处理。一个经典的例子是:
if <dir does not exist>:
<create dir> # May still fail if another process creates the target dir
因为在这种情况下,LBYL并不能排除异常的发生,所以它没有实际的好处,也没有什么判断可做:EAFP是唯一能正确处理竞争条件的方法。
但是如果没有竞争条件,两种方法都有可能有效。它们各有优缺点:
- 如果没有抛出异常,那么EAFP几乎是免费的
- 然而,如果发生异常,它的成本相对较高,因为需要处理很多内容,比如回溯栈、创建异常以及与异常处理条款进行比较
- 相比之下,LBYL可能会产生较高的固定成本:无论成功与否,额外的检查总是会执行
这就引出了以下决策标准:
- 这段代码是否被认为对应用程序的速度至关重要?如果不是,那就不用担心哪种方法更快,而是关注哪种方法更容易阅读。
- 预检查的成本是否高于抛出和捕获异常的成本?如果是,那么EAFP总是更快,应该使用。
- 如果答案是否定的,情况就更有趣了。在这种情况下,哪种方法更快将取决于成功情况还是错误情况更常见,以及预检查和异常处理的相对速度。要明确回答这个问题,需要进行实际的时间测量。
作为一个粗略的经验法则:
- 如果存在潜在的竞争条件,使用EAFP
- 如果速度不是关键,随便用你认为更容易阅读的那种
- 如果预检查的成本很高,使用EAFP
- 如果你预计操作大多数时候会成功*,使用EAFP
- 如果你预计操作失败的次数超过一半,使用LBYL
- 如果不确定,就测量一下
*人们对“多数时候”的定义可能会有所不同。对我来说,如果我预计操作成功的次数超过一半,我通常会使用EAFP,直到我有理由怀疑这段代码是一个实际的性能瓶颈。