繁体   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