是否有一种更具pythonic风格/更高效的方法来循环包含列表的字典,而不是使用for循环?
在使用是否有一种更具pythonic风格/更高效的方法来循环包含列表的字典,而不是使用for循环?,python,python-2.7,list,loops,for-loop,Python,Python 2.7,List,Loops,For Loop,在使用get以JSON格式从API提取信息之后,我现在尝试以高效的方式计算price的平均值 数据(API调用响应示例): 我通过以下代码成功地做到了这一点: 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
get
以JSON
格式从API
提取信息之后,我现在尝试以高效的方式计算price
的平均值
数据
(API调用响应示例):
我通过以下代码成功地做到了这一点:
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
然而,由于检索到的数据
字典很大,因此在显示输出之前似乎有相当长的等待时间
因此,我想知道是否有一种更有效和/或类似于python的方法可以在更短的时间内实现相同的结果。首先,你不是在循环一个dict,而是在循环一个恰好位于dict内部的列表 第二,为列表中的每个值做一些事情本质上需要访问列表中的每个值;没有办法绕过线性成本 因此,唯一可用的是微优化,如果你的代码太慢,可能不会有太大的区别,快10%没有帮助,如果你的代码已经足够快,你不需要它,但偶尔需要它们 在这种情况下,几乎所有的微优化也使代码更具可读性和python风格,因此没有理由不这样做:
首先,您要访问
数据[“数据”][“销售”]
两次。这样做的性能成本可能可以忽略不计,但也会降低代码的可读性,因此,让我们来解决这个问题:
sales = data["data"]["sales"]
接下来,与在范围内(0,len_sales)为i循环:
仅用于sales[i]
,相比,仅循环sales
,速度更快,可读性更强:
for sale in sales:
total_p += float(sale["price"])
现在我们可以将这个循环转化为一种理解,它的效率稍微高一点(尽管添加一个生成器的成本部分抵消了这一点,您可能真的想要测试这个生成器):
…并将其直接传递给sum
:
total_p = sum(float(sale["price"]) for sale in sales)
我们还可以使用Python附带的函数,而不是手动执行:
average = statistics.mean(float(sale["price"]) for sale in sales)
…除了您显然在使用Python2之外,所以您需要安装脱离PyPI的(官方的后端口仅可追溯到3.1;2.x版本已被放弃),所以让我们跳过这一部分
总而言之:
sales = data["data"]["sales"]
total = sum(float(sale["price"]) for sale in sales)
average = total / len(sales)
有几件事可能会有所帮助,如果这很重要,您肯定会希望使用
timeit
进行测试:
您可以使用获取价格
项目。这意味着您的表达式现在只链接两个函数调用,这意味着您可以链接两个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)))
对代码进行基准测试的最佳方法是使用模块,或者,如果您使用的是IPython,则使用
%timeit
magic。其工作原理如下:
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
因此,在我的笔记本电脑上,使用您的示例数据:
- 直接在
上循环并使用生成器表达式而不是语句大约快35%sales
- 使用列表理解而不是genexpr比这快约1%
- 使用
和map
代替genexpr大约快10%itemgetter
- 将其包装在函数中并缓存局部变量会使速度稍慢一些。(正如上面提到的,这并不奇怪,由于
,我们只对每个名称进行了一次查找,所以我们只是增加了一点开销,可能带来了0的好处。)map
sum(map(…map(…))
在我的笔记本电脑上是这个特定输入的最快版本
但当然,您需要在真实环境中使用真实输入重复此测试。当差异小到10%时,你不能仅仅假设细节会转移
还有一件事:如果您真的需要加快速度,通常最简单的方法是使用完全相同的代码,而不是使用通常的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%的改进要好得多。您可以使用一个名为的库,找到销售列表的平均值。要获得销售清单,您可以进行列表理解-
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)
你会得到平均价格。使用列表理解,您已经优化了代码。查看并阅读答案的最后一段您能发布有效数据吗?非常感谢您的帮助!我真的很感谢你为这个答案投入的时间和细节:)祝你有一个愉快的一天。关于你最近的编辑,你有没有机会编辑这篇文章来展示“最快”的解决方案?谢谢:)太好了!谢谢,太棒了!
mean(prices)
mean_price = sum(prices) / len(prices)