簡體   English   中英

是否有更多pythonic /更有效的方法來循環包含列表的字典而不是使用for循環?

[英]Is there a more pythonic/more efficient way to loop through dictionary containing lists rather than using for loops?

在使用getJSON格式從API提取信息之后,我現在嘗試以有效的方式計算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字典很大,因此在顯示輸出之前似乎有相當多的等待時間。

因此,我想知道是否有更高效和/或pythonic的方式來實現相同的結果,但是在更短的時間內。

首先,你沒有循環通過一個字典,你正在循環一個恰好在dict中的列表。

其次,為列表中的每個值執行某些操作本身就需要訪問列表中的每個值; 沒有辦法繞線性成本。

因此,唯一可用的是微優化,這可能不會有太大的區別 - 如果你的代碼太慢,10%的速度沒有幫助,如果你的代碼已經足夠快,你不需要它 - 但有時他們是需要的。

在這種情況下,幾乎所有的微優化也使你的代碼更具可讀性和Pythonic,所以沒有充分的理由這樣做:


首先,您要訪問data["data"]["sales"]兩次。 它的性能成本可能是微不足道的,但它也使你的代碼可讀性降低,所以讓我們解決這個問題:

sales = data["data"]["sales"]

接下來,而不是for i in range(0, len_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的非官方backport (官方stats backport只返回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 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

因此,在我的筆記本電腦上,您的樣本數據:

  • 直接在sales循環並使用生成器表達式而不是語句大約快35%。
  • 使用列表推導而不是genexpr比這快約1%。
  • 使用mapitemgetter而不是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%的改善好很多。

您可以使用名為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)

你會得到平均價格。 使用列表推導,您已經優化了代碼。 請參閱此內容並閱讀答案的最后一段

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM