不理解这个带有 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

将带有字典列表的 defaultdict(list) 字典转换为 csv 的最佳方法

python 中的defaultdict 用法

了解 Python 中 defaultdict 的使用 [重复]

defaultdict

将 `defaultdict` 公开为常规 `dict`