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

标签 python python-2.7 list loops for-loop

在使用getAPI格式的JSON中提取信息之后,我现在尝试以有效的方式计算price的平均值。
data(来自API调用的响应示例):

...
{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

然而,由于检索的data字典的大小很大,所以在显示输出之前似乎有相当长的等待时间。
因此,我想知道是否有一种更有效和/或Python的方式来实现相同的结果,但在更短的时间内。

最佳答案

首先,你不是在一个dict中循环,而是在一个恰好在dict中的列表中循环。
其次,为列表中的每一个值做某事本质上要求访问列表中的每一个值;没有办法绕过线性成本。
所以,唯一可用的是微优化,如果你的代码太慢,10%的速度没有用,那么如果你的代码已经足够快,你不需要它,但是偶尔需要它,这可能不会有太大的区别。
在这种情况下,几乎所有的微优化也使代码更加可读和Pythonic,所以没有好的理由不去做它们:
首先,您要访问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)

…除了您显然使用的是Python2,所以您需要在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,我们只对每个名称进行了一次查找,所以我们只增加了一点开销,可能没有什么好处。)
总的来说,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快了20X。2000%的改善比35%的改善要好得多。

关于python - 有没有一种比pythonic/更有效的方法来遍历包含列表的字典,而不是使用for循环?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51459559/

相关文章:

python - 在创建django时添加注册

python - SQLAlchemy 对删除的循环依赖

python - 按 pandas 中的排名求和值

python - Qt 现在在 LGPL 下发布,你会推荐它而不是 wxWidgets 吗?

python - 二进制到 Python 中的字符串/文本

python - 什么是rhel 7.X 服务器上最新的正式python3 安装

java - 创建注释类实例的数组列表

arrays - Python 2.7 : looping over 1D fibers in a multidimensional Numpy array

list - Lisp : (A (B C)), 为什么 1 个列表和 1 个原子?

Java - 将对象列表映射到具有其属性属性值的列表