理解嵌套defaultdict:`tree = lambda: defaultdict(tree)`与`tree = defaultdict(lambda: tree)`的差异及原理
搞懂嵌套
defaultdict的两种写法:有效 vs 无效 让我一步步帮你拆解这两种写法的区别,搞清楚为什么一个能正常工作,另一个会抛出循环引用的错误~
一、有效写法的工作原理
先看这个能正常生成多层嵌套字典的写法:
tree = lambda: defaultdict(tree) x = tree() x["1"] x["2"] x["1"]["3"] print(json.dumps(x))
这里的核心是tree是一个匿名函数,它的唯一作用就是返回一个新的defaultdict,而这个defaultdict的默认工厂正好是tree自己。
拆解执行的细节:
- 当你调用
tree()时,会创建一个全新的defaultdict实例,这个实例的规则是:如果访问了不存在的键,就自动调用tree()生成另一个全新的defaultdict作为该键的值。 - 比如
x["1"]:因为x是tree()生成的defaultdict,访问"1"这个不存在的键时,会触发tree(),生成一个新的defaultdict赋值给x["1"];后续x["1"]["3"]也是同理,再生成一层全新的defaultdict。 - 整个结构里的每一层都是独立的新实例,没有任何地方指向已经存在的实例,所以
json.dumps可以顺利序列化,不会出现循环引用问题。
二、无效写法报错的根本原因
再看这个会抛出循环引用错误的写法:
tree = defaultdict(lambda: tree) x = tree x["1"] x["2"] x["1"]["3"] print(json.dumps(x))
问题就出在循环引用的生成:
- 这里的
tree直接就是一个defaultdict实例,它的默认工厂是lambda: tree——意思是,当访问不存在的键时,不会生成新的实例,而是直接返回tree这个已经存在的实例本身。 - 举个例子:当你访问
x["1"]时,因为x就是tree实例,所以会调用lambda: tree,把tree自己赋值给x["1"];之后x["1"]["3"]其实就是tree["3"],同样会把tree自己赋值给tree["3"]。 - 这下就形成了死循环:
tree["1"]指向tree,tree["3"]也指向tree,整个结构是自己引用自己的循环体。json.dumps在序列化时会检测到这种循环结构,所以直接抛出ValueError: Circular reference detected。
三、修改后的写法为什么和第一种等价
你提到的修改后的写法:
tree = lambda: defaultdict(lambda: tree())
其实这个写法和最初的有效写法是完全等价的。因为tree本身就是一个返回defaultdict(tree)的函数,tree()调用后得到的就是一个新的defaultdict实例,所以lambda: tree()和原来的tree功能完全一致——都是返回一个全新的、默认工厂为tree的defaultdict。本质上就是换了一种写法表达同一个逻辑,所以自然也能正常生成多层嵌套的字典。
内容的提问来源于stack exchange,提问作者joseville
相关产品推荐
相关产品推荐

