【问题标题】:Python how to create a dict of dict of list with defaultdictPython如何使用defaultdict创建列表的dict
【发布时间】:2015-10-12 12:14:27
【问题描述】:

如何使用 defaultdict 创建列表 dict 的 dict?我收到以下错误。

>>> from collections import defaultdict
>>> a=defaultdict()
>>> a["testkey"]=None
>>> a
defaultdict(None, {'testkey': None})
>>> a["testkey"]["list"]=[]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object does not support item assignment

【问题讨论】:

  • a["testkey"] 是 None,你不能像字典一样使用它
  • 请注意,即使您使用defaultdict,为键分配其他内容(例如您的a["testkey"] = None)也会替换默认值。
  • 谢谢@jonrsharpe,我会注意的。

标签: python dictionary data-structures defaultdict


【解决方案1】:

这有点棘手。你创建一个 defaultdicts 的 defaultdict,像这样:

defaultdict(lambda: defaultdict(list))

【讨论】:

  • 这很好(投票赞成),但因为我有一种病态,即尽可能避免使用lambda 函数(在总是有更好选择的情况下,它们经常被滥用,例如使用@ 987654325@/filterlambda总是比内联 lambda 正文的 listcomp/genexpr 慢;我对上述滥用的反应是尽可能避免使用它们),这种方法使用空defaultdict(list) 的绑定方法来避免lambda 并将所有工作推送到C 层,这(通常快5-10%)提高了运行速度:defaultdict(defaultdict(list).copy)
  • 嘿,好主意!我也讨厌 lambda。随意将其编辑到答案中,或添加您自己的答案。
  • @wim: Sure, why not?在顶层造成我的强迫症。 :-)
  • +1 为您服务。也许你会喜欢我讨厌 lambda 的问题here
【解决方案2】:

比使用lambda 稍微快一点:

defaultdict(defaultdict(list).copy)

这与wim's answer 具有相同的可观察行为,但避免使用lambda,而支持在 C 中实现的(在 CPython 中)绑定内置方法,这意味着默认值生成不必执行任何 Python字节码或查找任何名称,它的运行速度要快一些。在 CPython 3.5 上的微基准测试中,在访问时密钥不存在时所支付的成本似乎比其他等效的 lambda 低 5-10%。

真的,我喜欢它的原因是因为我讨厌lambda,因为人们过度使用它是一个坏主意(例如map/filterlambda 总是比等效的listcomp 更冗长和更慢/genexpr,但人们仍然无缘无故地继续这样做),即使在这种情况下它并不重要。


更新:从 3.8 开始,这种性能改进已经消失,lambda 更快(在 3.8 上使用 lambda 减少了约 3% 的运行时间,在 3.9 上减少了约 7%),对于带有ipython 的简单微基准测试。如果你想重现我的测试,我测试了:

>>> from collections import defaultdict
>>> %%timeit dd = defaultdict(lambda: defaultdict(list)); o = object
... dd[o()]

>>> %%timeit dd = defaultdict(defaultdict(list).copy); o = object
... dd[o()]

缓存o = object 最大限度地减少了查找费用,并允许我们在不做任何其他工作的情况下制作非常便宜、有保证的唯一键(强制list 自动激活)。

3.8 中的性能改进可能很大程度上是由于引入了per opcode cache for the LOAD_GLOBAL instruction,从而减少了在lambda 中查找list 的成本,而不是完整的dict 查找(在这种情况下是两个) list,内置)快速检查 dict 上的版本标记,然后从缓存中进行廉价加载,将成本降低约 40%。 3.9 的改进可能(对此不确定)与 CPython 的内部结构转向优化和支持向量调用代码路径更多,代价是非向量调用代码路径(相对而言,defaultdict(list).copy 路径使用更多),甚至在这些改进之前,defaultdict(list).copy 有一些 lambda 缺乏的低效率,为改进提供了一些余地。

【讨论】:

  • 您好,您能展示一下您的基准吗?我无法重现速度提升
  • @wim:这只是一个 ipython 微基准测试,故意敲击“自动激活”案例,就像我说的那样,即使那样,收益也很小。只有两行 %%timeit -r5 dd = defaultdict(defaultdict(list).copy); k = itertools.count(10000).__next__,“正文”只是 dd[k()]。在我的机器上,Linux x86-64,Python 3.5,每个循环的成本是 504 ns;使用lambda: defaultdict(list) 是 559 ns。就像我说的,没什么大不了的。我的主要动机(例如,我实际上并不认为 lambda 在这里不好)是 lambda 讨厌(嘿,它也更短!),而不是微优化。 :-)
  • 您可能希望通过提及 python 版本来限定答案中提到的性能提升。
  • 尽管我很喜欢这个答案,但性能提升似乎已经消失了。 lambda 似乎可靠地快了大约 10%(Linux 上的 CPython 3.9)。
  • @wim:是的,可以确认。猜测(根据代码检查),更改是:1)3.8 为LOAD_GLOBAL 添加了每个操作码缓存,这大大降低了查找defaultdict/list 的查找成本lambda,以及 2 ) 用于复制的代码路径 defaultdict.copylambda 基本相同(比缓存的 LOAD_GLOBAL 略便宜,但没有那么多),但使用基于 C 可变参数的函数调用技术 (PyObject_CallFunctionObjArgs ) 这比字节码解释器循环使用的更优化的路径更通用(也更慢)。
【解决方案3】:

你可能不得不这样做。

>>> from collections import defaultdict
>>> a=defaultdict()
>>> a["testkey"]=None
>>> a["testkey"]=defaultdict(list)
>>> a["testkey"]["list"]=["a","b","c"]
>>> a
defaultdict(None, {'testkey': defaultdict(<type 'list'>, {'list': ['a', 'b', 'c']})})

【讨论】:

  • 不,你不需要,你可以将自定义函数传递给第一个defaultdict
  • 谢谢@jonrsharpe,但我在我的控制台上试过了,它奏效了。这是错误的做法吗?
  • 它会起作用,但是将defaultdict 和这样的显式值设置混合起来有点尴尬。 a["testkey"]=None 也是完全多余的。
  • 我明白了。所以我猜defaultdict() 中的 lambda 函数是最好的方法?
  • 感谢@nohup 的回答,+1 使它工作,但我更喜欢使用 lambda 函数以函数方式使用它。
猜你喜欢
  • 2019-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-25
  • 2015-08-08
  • 2021-07-11
  • 2013-12-24
  • 1970-01-01
相关资源
最近更新 更多