Python - 捕获异常的效率

时间:2013-01-19 20:04:48

标签: python performance try-catch

  

可能重复:
  Python FAQ: “How fast are exceptions?”

我记得读过Python实现了一个"更好地寻求宽恕而不是要求许可"关于例外的哲学。根据作者的说法,这意味着Python代码应该使用大量的try - except子句,而不是试图提前确定是否要做一些会导致异常的事情。

我刚刚在我的网络应用程序上写了一些try - except子句,其中大部分时间都会在代码运行时引发异常。因此,在这种情况下,提高和捕获异常将是常态。从效率的角度来看这是不是很糟糕?我还记得有人告诉我,捕获一个引发异常会产生很大的性能开销。

使用try - except条款是否会产生不必要的低效率,在这些条款中,您希望几乎所有时间都会引发异常并将其捕获?

这里是代码 - 它使用Django ORM来检查将用户与各种第三方社交提供商相关联的对象。

try:
    fb_social_auth = UserSocialAuth.objects.get(user=self, provider='facebook')
    user_dict['facebook_id'] = fb_social_auth.uid
except ObjectDoesNotExist:
    user_dict['facebook_id'] = None

try:
    fs_social_auth = UserSocialAuth.objects.get(user=self, provider='foursquare')
    user_dict['foursquare_id'] = fs_social_auth.uid
except ObjectDoesNotExist:
    user_dict['foursquare_id'] = None

try:
    tw_social_auth = UserSocialAuth.objects.get(user=self, provider='twitter')
    user_dict['twitter_id'] = tw_social_auth.uid
except ObjectDoesNotExist:
    user_dict['twitter_id'] = None

第一个将很少采取例外,因为现在我们正在执行"登录Facebook"作为新用户加入网站的主要方法。但是,Twitter和Foursquare是可选的,如果他们想要导入朋友或粉丝,我希望大多数人不会。

我愿意接受更好的方法来编写这种逻辑。

3 个答案:

答案 0 :(得分:6)

无论何时编码都存在一个平衡的问题:性能,可读性,正确性,可扩展性,可维护性等。 遗憾的是,通常无法同时改进这些方向中的每个方向的代码。例如,什么是快速可能不那么可读。

在Python中鼓励try..except的原因之一是因为您经常无法预测代码的所有使用方式,因此不是检查是否存在特定条件,更常见的是捕获任何可能出现的某类错误。因此try..except可能会使您的代码更具可重用性。

但是,如果经常达到try..except子句,except也很慢。

有没有办法对该块进行编码,以便不会引发异常并使用try..except来捕获较不频繁的条件?

如果不是,为了提高效率,您可以选择不使用try..except。编程中几乎没有硬性和快速的规则。您必须根据您的平衡点选择自己的方式。

答案 1 :(得分:2)

如果您尝试针对速度优化此功能,则应关注可能是实际瓶颈的问题。您的三个数据库查询(每个查询都会导致操作系统进行上下文切换)几乎肯定比捕获异常要长一个数量级。如果要尽可能快地编写代码,请首先将所有三个数据库查询合并为一个:

auth_objects = UserSocialAuth.objects.filter(user=self, provider__in=('facebook', 'foursquare', 'twitter'))

然后遍历对象。如果这三个提供者是数据库中唯一的提供者,则provider__in过滤器可能是不必要的。

答案 2 :(得分:2)

确实,捕获一个异常是相当昂贵的(参见下面的某些时间),你不希望在你的程序的瓶颈中这样做,但在你给出的例子中,捕获异常将是一个与调用Model.objects.get相比,运行时的一小部分,它必须构建一个SQL查询,将其传输到数据库服务器,并等待数据库报告没有这样的对象。

一些示例时间。函数f2抛出并捕获异常,而f1实现相同的功能而不使用异常。

d = dict()

def f1():
    if 0 in d: return d[0]
    else: return None

def f2():
    try: return d[0]
    except KeyError: return None

>>> timeit(f1)
0.25134801864624023
>>> timeit(f2)
2.4589600563049316

并且f3尝试通过Django的ORM从数据库(在同一台机器上运行)中获取一个不存在的对象:

def f3():
    try:
        MyModel.objects.get(id=999999)
    except MyModel.DoesNotExist:
        pass

这比f2长约400倍(只要我不想等待默认的number=1000000次迭代完成):

>>> timeit(f3, number=1000)
1.0703678131103516