【问题标题】:don't understand this lambda expression with defaultdict不理解这个带有 defaultdict 的 lambda 表达式
【发布时间】: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 如果没有递归定义,你应该如何定义递归数据类型?

标签: python python-3.x lambda defaultdict


【解决方案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】:

    注意两点:

    1. lambda 代表一个匿名函数。
    2. 函数是 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。
      猜你喜欢
      • 2017-04-05
      • 2021-11-28
      • 1970-01-01
      • 1970-01-01
      • 2013-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-29
      相关资源
      最近更新 更多