簡體   English   中英

Python 中的 functools.reduce 無法按預期工作

[英]functools.reduce in Python not working as expected

我想使用functools.reduce函數對嵌套在列表中的字典鍵求和

我可以使用以下簡單程序在沒有functools.reduce函數的情況下完成此操作:

dict1 = {'a': '1', 'b': '2'}
dict2 = {'a': '5', 'b': '0'}
dict3 = {'a': '7', 'b': '3'}

data_list = [dict1, dict2, dict3]

total_a = 0
total_b = 0
for record in data_list:
    total_a += eval(record['a'])
    total_b += eval(record['b'])

print(total_a)
print(total_b)

然而,正如我所說,我想使用functools.reduce方法來產生相同的結果。

這是我嘗試將functools.reduce與 lambda 表達式一起使用:

from functools import reduce

dict1 = {'a': '1', 'b': '2'}
dict2 = {'a': '5', 'b': '0'}
dict3 = {'a': '7', 'b': '3'}

data_list = [dict1, dict2, dict3]

total_a = reduce(lambda x, y: int(x['a']) + int(y['a']),data_list)
total_b = reduce(lambda x, y: int(x['b']) + int(y['b']),data_list )

print(total_a)
print(total_b)

不幸的是,我收到以下錯誤,不知道為什么:

TypeError: 'int' object is not subscriptable

有人知道我為什么會收到此錯誤嗎?

TypeError: 'int' object is not subscriptable

有人知道我為什么會收到此錯誤嗎?

首先,讓我們將樣本減少(雙關語)到最低限度:

>>> from functools import reduce
>>> data = [{"a": 1}, {"a": 2}, {"a": 3}]
>>> reduce(lambda x, y: x["a"] + y["a"], data)   
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
TypeError: 'int' object is not subscriptable

同樣的錯誤。 但請注意:

>>> reduce(lambda x, y: x["a"] + y["a"], data[:2])   
3

那行得通。 發生什么了? 為簡化起見,讓我們將 lambda 表達式分配給一個變量:

f = lambda x, y: x['a'] + y['a']

Reduce 像這樣組合輸入:

# Example: reduce(lambda x, y: x["a"] + y["a"], data[:2])   
>>> f(data[0], data[1])

# which evaluates in steps like this:

>>> data[0]["a"] + data[1]["a"]
>>> 1 + 2
>>> 3

但是在減少完整列表時會發生什么? 這評估為

# Example: reduce(lambda x, y: x["a"] + y["a"], data)   
>>> f(f(data[0], data[1]), data[2])

# which evaluates in steps like this:

>>> f(data[0]["a"] + data[1]["a"], data[2])
>>> f(1 + 2, data[2])
>>> f(3, data[2])
>>> 3["a"] + data[2]["a"]

所以這會出錯,因為它試圖從整數 3 訪問項目"a"

基本上:傳遞給 reduce 的函數的輸出必須是可接受的,因為它是第一個參數。 在您的示例中,lambda 需要一個字典作為其第一個參數並返回一個整數。

歸約函數接收當前歸約值加上要歸約的下一個迭代項。 訣竅在於選擇減少值的樣子。 在您的情況下,如果您選擇一個包含 'a' 和 'b' 的縮減值的 2 項列表,則縮減函數只會將下一個 'a' 和 'b' 添加到這些值。 減少最容易寫成幾個語句,因此應該從匿名 lambda 移動到常規函數。 [0, 0]的初始值設定項開始保存縮減后的 'a' 和 'b',你會得到:

from functools import reduce

def reducer(accum, next_dict):
    print(accum, next_dict) # debug trace
    accum[0] += int(next_dict['a'])
    accum[1] += int(next_dict['b'])
    return accum

dict1 = {'a': '1', 'b': '2'}
dict2 = {'a': '5', 'b': '0'}
dict3 = {'a': '7', 'b': '3'}

data_list = [dict1, dict2, dict3]

total_a, total_b = reduce(reducer, data_list, [0, 0])

您誤解了reduce()的工作原理。 在傳遞給它的函數中,它的第一個參數是迄今為止的部分結果,與傳遞給reduce()的迭代沒有直接關系。 可迭代對象一次將一個元素傳遞給函數的第二個參數。 由於您想要一個總和,因此“部分結果”的初始值需要為 0,這也需要傳遞給reduce()

因此,總而言之,這些行將打印您想要的內容:

print(reduce(lambda x, y: x + int(y['a']), data_list, 0))
print(reduce(lambda x, y: x + int(y['b']), data_list, 0))

編輯:用上面的int()替換eval() ,因此它與編輯的問題匹配。 不過,這與答案無關。

編輯2:您不斷更改問題,但我不會不斷更改答案以匹配;-) 上面的代碼完全回答了問題的早期版本,並且沒有任何材料發生變化。 完全相同的事情仍在起作用,並且需要完全相同的方法。

類型上的光澤

雖然 Python 不需要顯式類型聲明,但有時它們會有所幫助。

如果您有一個A類型的可迭代交付對象,並且reduce()的結果是B類型,那么傳遞給reduce()的第一個參數的簽名必須是

def reduction_function(x: B, y: A) -> B

在示例中, AdictBint 為兩者傳遞一個字典是不可能的。 這就是為什么我們需要在這種情況下指定類型B ( int ) 的初始值。

在文檔示例中, AB通常都是intfloat 那么對於reduce()的第一個參數,一個簡單的 + 或 * 已經是類型正確的。

由於我們要添加整數,因此reduce的替代方法是sum

data_list = [{'a': '1', 'b': '2'}, {'a': '5', 'b': '0'}, {'a': '7', 'b': '3'}]

total_a, total_b = map(sum, zip(*((int(d['a']),int(d['b'])) for d in data_list)))

print(total_a, total_b)
# 13 5

暫無
暫無

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

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