在使用get
以API
格式从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的方式来实现相同结果,但时间更短。
答案 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%。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%的改善要好得多。
答案 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,并阅读答案的最后一段