不理解这个带有 defaultdict 的 lambda 表达式
Posted
技术标签:
【中文标题】不理解这个带有 defaultdict 的 lambda 表达式【英文标题】:don't understand this lambda expression with defaultdict 【发布时间】:2019-01-20 13:18:03 【问题描述】:我在 pythontips 看到了这个例子。当 defaultdict 接受参数“tree”并返回“tree”时,我不明白第二行。
import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['color']['favor'] = "yellow"
# Works fine
运行这段代码后,我检查了 some_dict 的类型
defaultdict(< function < lambda > at 0x7f19ae634048 >,
'color': defaultdict(
< function < lambda > at 0x7f19ae634048 >, 'favor': 'yellow'))
【问题讨论】:
顺便说一句,小费来自book.pythontips.com/en/testing/collections.html 这段代码很聪明。也许太聪明了。 它并不比 Python 中的任何递归函数更聪明。它只是使用当前正在定义的函数将分配给的名称。 那个递归函数在defaultdict
内部被调用,这就是它如此聪明的原因。
@Gabriel 如果没有递归定义,你应该如何定义递归数据类型?
【参考方案1】:
这是创建递归defaultdict
的一种非常聪明的方法。一开始理解起来有点棘手,但一旦深入了解发生了什么,它实际上是递归的一个非常简单的用法。
在本例中,我们定义了一个递归 lambda 函数 tree
,它返回一个 defaultdict
,其构造函数为 tree
。为了清楚起见,让我们使用常规函数重写它。
from collections import defaultdict
from pprint import pprint
def get_recursive_dict():
return defaultdict(get_recursive_dict)
请注意,我们返回的是 defaultdict(get_recursive_dict)
而不是 defaultdict(get_recursive_dict())
。我们想要传递defaultdict
一个可调用对象(即函数get_recursive_dict
)。实际上调用get_recursive_dict()
会导致无限递归。
如果我们调用get_recursive_dict
,我们会得到一个空的defaultdict
,它的默认值是函数get_recursive_dict
。
recursive_dict = get_recursive_dict()
print(recursive_dict)
# defaultdict(<function get_recursive_dict at 0x0000000004FFC4A8>, )
让我们看看它的实际效果。创建键'alice'
,其对应的值默认为空defaultdict
,其默认值为函数get_recursive_dict
。请注意,这与我们的recursive_dict
相同的默认值!
print(recursive_dict['alice'])
# defaultdict(<function get_recursive_dict at 0x0000000004AF46D8>, )
print(recursive_dict)
# defaultdict(<function get_recursive_dict at 0x0000000004AF46D8>, 'alice': defaultdict(<function get_recursive_dict at 0x0000000004AF46D8>, ))
所以我们可以创建任意数量的嵌套字典。
recursive_dict['bob']['age'] = 2
recursive_dict['charlie']['food']['dessert'] = 'cake'
print(recursive_dict)
# defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, 'charlie': defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, 'food': defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, 'dessert': 'cake')), 'bob': defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, 'age': 2), 'alice': defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, ))
一旦你用一个键覆盖了默认值,你就不能再创建任意深度的嵌套字典了。
recursive_dict['bob']['age']['year'] = 2016
# TypeError: 'int' object does not support item assignment
我希望这能解决问题!
【讨论】:
【参考方案2】:注意两点:
lambda
代表一个匿名函数。
函数是 Python 中的一等对象。它们可以像任何其他对象一样分配给变量。
所以这里有两种不同的方法来定义功能相同的对象。它们是递归函数,因为它们引用了自己。
from collections import defaultdict
# anonymous
tree = lambda: defaultdict(tree)
# explicit
def tree(): return defaultdict(tree)
依次使用这些不同的定义运行最后两行,您只会看到 defaultdict
类型的命名存在细微差别:
# anonymous
defaultdict(<function __main__.<lambda>()>,
'color': defaultdict(<function __main__.<lambda>()>,
'favor': 'yellow'))
# explicit
defaultdict(<function __main__.tree()>,
'color': defaultdict(<function __main__.tree()>,
'favor': 'yellow'))
【讨论】:
【参考方案3】:如果你试试这个更容易看到:a = lambda: a
,你会看到a()
返回a
。所以...
>>> a = lambda: a
>>> a()()()()
<function <lambda> at 0x102bffd08>
他们也在使用defaultdict
进行此操作。 tree
是一个返回 defaultdict 的函数,其默认值是另一个 defaultdict,依此类推。
我实际上也没有意识到这一点。我认为必须首先定义tree
。也许这是一个特殊的 Python 规则? (编辑:)不,我忘记了 Python 在运行时进行名称查找,然后 tree
已经指向 lambda。在 C++ 中有编译时引用检查,但您可以定义引用自身的函数。
这似乎是一种创建某些用户意想不到的行为的方法。就像说你后来不小心重新定义了tree
,你的defaultdict被打破了:
>>> import collections
>>> tree = lambda: collections.defaultdict(tree)
>>> some_dict = tree()
>>> tree = 4
>>> some_dict[4][3] = 2 # TypeError: first argument must be callable or None
【讨论】:
没什么特别的;当需要一个值时会进行名称查找,直到函数被调用时才需要该值。 没有特殊的python规则。运行 lambda 时,tree
已定义。
对,这是有道理的。所以现在你依靠a
不会改变。如果你这样做 a = lambda: a
然后 b = a
然后 a = 3
, b()
现在将返回 3。以上是关于不理解这个带有 defaultdict 的 lambda 表达式的主要内容,如果未能解决你的问题,请参考以下文章
将带有字典列表的 defaultdict(list) 字典转换为 csv 的最佳方法