我想将所有字典值联合起来,在本例中为集合。只有当输入列表中正好有两个字典时,我才会得到预期的结果。
输入列表中的两个词典会产生预期的结果:
>>> reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}, {'a': {3, 4}}])
set([1, 2, 3, 4])
输入列表中的三个字典会产生类型错误。
预期成果:set([1, 2, 3, 4, 5, 6])
>>> reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
Traceback (most recent call last):
File "<input>", line 1, in <module>
reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
File "<input>", line 1, in <lambda>
reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
TypeError: 'set' object has no attribute '__getitem__'
输入列表中的一个词典生成一个词典而不是一个集合。
预期成果:set([1, 2])
>>> reduce((lambda x, y: x['a'] | y['a']), [{'a': {1, 2}}])
{'a': set([1, 2])}
空输入列表也会产生不同的类型错误。
预期成果:set([])
>>> reduce((lambda x, y: x['a'] | y['a']), [])
Traceback (most recent call last):
File "<input>", line 1, in <module>
reduce((lambda x, y: x['a'] | y['a']), [])
TypeError: reduce() of empty sequence with no initial value
我需要帮助来了解我做错了什么以及为什么会产生这些结果。
TLDR:
reduce(function, iterable)
调用以递归方式function
应用于iterable
元素和之前的结果。这意味着function
的返回类型必须是有效的输入类型!
- 在您的情况下,
function
期望dict
,但会产生set
。由于不可能在set
上呼叫x['y']
,因此会引发TypeError
。 - 当
iterable
只有两个元素时,function
仅应用于一次且仅应用于这些元素。因此,永远不会遇到返回类型function
不是有效输入类型的问题。
您必须先从dict
map
到set
,然后reduce
set
。
reduce(lambda x, y: x | y, map(lambda x: x['a'], [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}]))
# merge via reduce ^ convert via map ^
为什么reduce
在某些情况下会失败
调用reduce(function, iterable)
执行以下代码的等效项:
def reduce(function, iterable, start=None):
result = next(iterable) if start is None else start # 1.
for element in iterable:
result = function(result, element) # 2.
return result
这会导致几种情况:
iterable
有一个元素,未设置start
result
是iterable
的第一个元素(1.
)- 从不调用
function
;其返回和输入类型无关紧要
- 从不调用
iterable
有两个元素,未设置start
result
是iterable
的第一个元素(1.
)function
在第一个元素和next
元素(2.
) 上调用function
永远不会收到自己的结果;它的返回类型毫无意义
iterable
具有两个以上的元素,并且未设置start
result
是iterable
的第一个元素(1.
)function
在第一个元素和next
元素上调用(2.
)function
在上一个结果和next
元素上调用 (2.
)function
接收自己的结果;其返回类型和输入类型必须匹配
iterable
为空或不为空,并设置start
- 同上,如果
start
是iterable
的第一个元素
- 同上,如果
iterable
为空且未设置start
- 无法设置
result
并引发TypeError
(1.
)
- 无法设置
在您的情况下,即:
- 两个词典是 2。 并按预期工作。
- 三个字典是 3。 并阻塞不兼容的输入和返回类型。
- 空输入列表为5。 并且因缺少输入而失败 - 正如预期的那样。
如何代替
映射/还原
您的reduce
实际上同时做两件事:它单独转换/提取每个元素,然后合并两个结果。这是一个经典的map/reduce任务:每个元素一个,所有元素一个。
您可以使用内置的map
和reduce
直接将其拆分为两个单独的操作:
sets = map(lambda x: x['a'], [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
result = reduce(lambda x, y: x | y, sets)
当然,您也可以直接嵌套这两个表达式。
理解/减少
map
部分可以用理解来表示。
sets = (x['a'] for x in [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])
result = reduce(lambda x, y: x | y, sets)
理解/作业
在 Python3.8 中,您也可以使用赋值表达式代替reduce
。
result = set()
result = [(result := (result | x['a'])) for x in [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}]]
使用 for 循环
只是,你知道,把它写出来。
result = set()
for element in [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}]:
result |= element['a']
reduce
的函数的输出必须与迭代器中的项具有相同的类型,以便它可以使用相同的函数不断聚合项值。
在你的例子中,lambda x, y: x['a'] | y['a']
的输出是一个集合{1, 2, 3, 4}
,所以当reduce
尝试用{1, 2, 3, 4}
聚合第三个{'a': {5, 6}}
时,它会失败,因为lambda函数将x
和y
都视为字典,并试图通过键'a'
获取每个项目,而集合没有。
至于TypeError: reduce() of empty sequence with no initial value
异常,你只需要为reduce
提供一个初始值作为第三个参数,在你的例子中应该是一个空集合{}
,但你只需要首先放弃传递给它的想法字典列表,而是传递给它一个集合列表。
reduce
迭代工作,它将在序列的项目之间应用减少聚合。例如,给定元素i
、j
和k
,连同函数foo
,它将处理foo(foo(i, j), k)
。
在您的示例中,foo(i, j)
工作正常,给出一个set
,但外部调用失败,因为结果是set
,没有密钥'a'
。后台[]
的语法调用__getitem__
,这就是您看到与此方法相关的错误的原因。
你能做些什么呢?
一个微不足道的技巧是让你的函数输出一个字典,然后直接访问它的唯一值。这可确保函数始终输出具有键'a'
的字典。
reduce((lambda x, y: {'a': x['a'] | y['a']}),
[{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}])['a']
# {1, 2, 3, 4, 5, 6}
更具可读性,您可以定义一个命名函数:
def foo(x, y):
return {'a': x['a'] | y['a']}
L = [{'a': {1, 2}}, {'a': {3, 4}}, {'a': {5, 6}}]
reduce(foo, L)['a']