有没有一种比pythonic /更有效的方法来遍历包含列表的字典,而不是使用for循环?

时间:2018-07-21 19:46:51

标签: python python-2.7 list loops for-loop

在使用getAPI格式从JSON提取信息之后,我现在尝试计算 高效 方式。

price(来自API调用的示例响应):

data

我设法使用以下代码做到了这一点:

...
{u'status': u'success', u'data': {u'context_id': u'2', u'app_id': u'123', u'sales': [{u'sold_at': 133, u'price': u'1.8500', u'hash_name': u'Xuan881', u'value': u'-1.00000'}, {u'sold_at': 139, u'price': u'2.6100', u'hash_name': u'Xuan881', u'value': u'-1.00000'},
... etc.

但是,由于检索到的len_sales = len(data["data"]["sales"]) total_p = 0 for i in range(0,len_sales): total_p += float(data["data"]["sales"][i]["price"]) average = total_p/len_sales print average 词典的大小很大,因此显示输出之前似乎有很多等待时间。

因此,我想知道是否存在一种更有效和/或pythonic的方式来实现相同结果,但时间更短。

2 个答案:

答案 0 :(得分:7)

首先,您没有遍历字典,而是遍历了位于字典内的列表。

第二,对列表中的每个值执行某些操作本质上要求访问列表中的每个值;线性成本无法解决。

因此,唯一可用的是微优化,这可能不会有太大的不同-如果您的代码太慢,那么快10%则无济于事,并且如果您的代码已经足够快,您就不会不需要它-但有时需要它们。

在这种情况下,几乎所有的微优化也使您的代码更具可读性和Python风格,因此没有充分的理由这样做:


首先,您两次访问data["data"]["sales"]。这样做的性能成本可以忽略不计,但这也使代码的可读性降低,因此,我们来解决此问题:

sales = data["data"]["sales"]

接下来,与其循环使用for i in range(0, len_sales):来循环sales[i],不如循环循环sales更快,而且可读性更高:

for sale in sales:
    total_p += float(sale["price"])

现在,我们可以将此循环转换为一种理解,这会稍微提高效率(尽管添加生成器的成本部分抵消了这种理解,您可能实际上想测试一下该生成器):

prices = (float(sale["price"]) for sale in sales)

…并将其直接传递给sum

total_p = sum(float(sale["price"]) for sale in sales)

我们也可以使用Python随附的mean函数,而不是手动执行:

average = statistics.mean(float(sale["price"]) for sale in sales)

…,除了您显然使用的是Python 2,因此您需要在PyPI上安装unofficial backport(官方stats反向移植仅返回3.1; 2.x版本是放弃),所以我们跳过这一部分。

将它们放在一起:

sales = data["data"]["sales"]
total = sum(float(sale["price"]) for sale in sales)
average = total / len(sales)

可能有帮助的两件事—如果重要的话,您肯定要使用timeit进行测试:

您可以使用operator.itemgetter来获取price项目。这意味着您的表达式现在仅链接了两个函数调用,这意味着您可以链接两个map调用:

total = sum(map(float, map(operator.itemgetter("price"), sales)))

对于那些不是来自Lisp背景的人来说,这可能比理解的可读性差,但这当然并不可怕,并且可能更快一些。


或者,对于中等大小的输入,构建临时列表有时是值得的。当然,您浪费了分配内存和复制数据的时间,但是迭代列表比迭代生成器要快,因此真正确定的唯一方法是进行测试。


可能会有所作为的另一件事是将整个事情移到一个函数中。顶层代码没有局部变量,只有全局变量,查找起来较慢。

如果您真的需要挤出最后几个百分点,有时甚至值得将float之类的全局函数和内置函数复制到本地变量中。当然,这对map并没有帮助(因为我们只访问它们一次),但是可能会有一个理解,所以我将继续说明如何做到这一点:

def total_price(sales):
    _float = float
    pricegetter = operator.itemgetter("price")
    return sum(map(_float, map(pricegetter, sales)))

基准测试代码的最佳方法是使用timeit模块,或者,如果您使用的是IPython,则使用%timeit魔术。像这样工作:

In [3]: %%timeit
... total_p = 0 
... for i in range(0,len_sales):
...     total_p += float(data["data"]["sales"][i]["price"])
10000 loops, best of 3: 28.4 µs per loop
In [4]: %timeit sum(float(sale["price"]) for sale in sales)
10000 loops, best of 3: 18.4 µs per loop
In [5]: %timeit sum(map(float, map(operator.itemgetter("price"), sales)))
100000 loops, best of 3: 16.9 µs per loop
In [6]: %timeit sum([float(sale["price"]) for sale in sales])
100000 loops, best of 3: 18.2 µs per loop
In [7]: %timeit total_price(sales)
100000 loops, best of 3: 17.2 µs per loop

因此,在我的笔记本电脑上,提供您的示例数据:

  • 直接在sales上循环并使用生成器表达式而不是语句,速度提高了约35%。
  • 使用列表推导代替genexpr的速度大约要快1%。
  • 使用mapitemgetter代替genexpr大约快10%。
  • 将其包装在函数中并缓存本地变量会使事情变慢。 (毫不奇怪,如上所述,感谢map,我们对每个名称都只进行了一次查找,因此我们只增加了一点点开销,可能会获得0的收益。)

总的来说,sum(map(…map(…)))是我笔记本电脑上为此特定输入禁食的食物。

但是,当然,您需要使用实际输入在实际环境中重复此测试。当只有10%的差异很重要时,您不能仅仅假设细节会转移。


另一件事:如果您确实需要加快处理速度,通常最简单的操作就是采用完全相同的代码并在PyPy中运行它,而不是通常的CPython解释器。重复上述一些测试:

In [4]: %timeit sum(float(sale["price"]) for sale in sales)
680 ns ± 19.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [5]: %timeit sum(map(float, map(operator.itemgetter("price"), sales)))
800 ns ± 24.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [6]: %timeit sum([float(sale["price"]) for sale in sales])
694 ns ± 24.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

现在,生成器表达式版本是最快的,但是更重要的是,这三个版本的速度大约是CPython中的20倍。 2000%的改善比35%的改善要好得多。

答案 1 :(得分:1)

您可以使用名为statistics的库来查找销售清单的均值。要获取销售清单,您可以进行清单理解-

prices = [float(v) for k, v in i.iteritems() for i in data["data"]["sales"] if k == "price"]

这将为您提供价格清单。现在,您需要对上述库进行的操作

mean(prices)

或者,您可以做类似-

mean_price = sum(prices) / len(prices)

您将获得平均价格。使用列表推导,您已经优化了代码。参见this,并阅读答案的最后一段