首先,你不是在循环一个字典,而是在循环一个恰好在字典中的列表。
其次,对于列表中的每个值都需要执行某些操作,这本质上需要访问列表中的每个值;没有办法避免线性成本。
因此,唯一可用的是微小的优化,这可能不会有太大的区别——如果您的代码太慢,那么快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上的非官方后移版本(官方的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%。
- 使用列表推导式而非生成器表达式,速度比前者快1%。
- 使用
map
和itemgetter
而非生成器表达式,速度快约10%。
- 将其包装在函数中并缓存局部变量会使事情稍微变慢。(不足为奇——如上所述,由于
map
,我们每个名称仅进行一次查找,因此我们只是为可能没有任何好处的微小开销添加了一个微小开销。)
总的来说,在我的笔记本上,对于这个特定的输入,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%的改进要好得多。