首先,您不是在遍历字典,而是在遍历恰好位于字典内的列表。
其次,为列表中的每个值做某事本质上需要访问列表中的每个值;没有办法绕过线性成本。
所以,唯一可用的就是微优化,这可能不会产生太大影响——如果你的代码太慢,快 10% 也无济于事,如果你的代码已经足够快,你就不用不需要它——但偶尔需要它们。
在这种情况下,几乎所有的微优化也会使您的代码更具可读性和 Python 风格,因此没有充分的理由不这样做:
首先,您访问了两次data["data"]["sales"]。这样做的性能成本可能可以忽略不计,但它也会降低代码的可读性,所以让我们来解决这个问题:
sales = data["data"]["sales"]
接下来,不是为了使用sales[i]而循环for i in range(0, len_sales):,而是循环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%。
- 使用列表推导而不是geneexpr 比这快大约1%。
- 使用
map 和itemgetter 代替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% 的改进要好得多。