栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

Python

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Python

在Python中实现嵌套字典的最佳方法是什么?

这是个坏主意,请不要这样做。相反,请使用常规词典并dict.setdefault在适当位置使用apropos,因此,在正常使用情况下缺少键时,你将获得期望的KeyError。如果你坚持要采取这种行为,请按以下步骤射击自己:

__missing__
在dict子类上实现以设置并返回新实例。

从Python 2.5开始,这种方法就已经可用(并记录在案),并且(对我来说特别有价值)它的打印效果与普通dict一样,而不是自动生成的defaultdict的丑陋打印:

class Vividict(dict):    def __missing__(self, key):        value = self[key] = type(self)() # retain local pointer to value        return value          # faster to return than dict lookup

(注意self[key]在作业的左侧,因此此处没有递归。)

并说你有一些数据:

data = {('new jersey', 'mercer county', 'plumbers'): 3,        ('new jersey', 'mercer county', 'programmers'): 81,        ('new jersey', 'middlesex county', 'programmers'): 81,        ('new jersey', 'middlesex county', 'salesmen'): 62,        ('new york', 'queens county', 'plumbers'): 9,        ('new york', 'queens county', 'salesmen'): 36}

这是我们的用法代码:

vividict = Vividict()for (state, county, occupation), number in data.items():    vividict[state][county][occupation] = number

现在:

>>> import pprint>>> pprint.pprint(vividict, width=40){'new jersey': {'mercer county': {'plumbers': 3, 'programmers': 81},     'middlesex county': {'programmers': 81,    'salesmen': 62}}, 'new york': {'queens county': {'plumbers': 9,          'salesmen': 36}}}

批评

对这种类型的容器的批评是,如果用户拼错了密钥,我们的代码可能会无声地失败:

>>> vividict['new york']['queens counyt']{}另外,现在我们的数据中会有一个拼写错误的县:>>> pprint.pprint(vividict, width=40){'new jersey': {'mercer county': {'plumbers': 3, 'programmers': 81},     'middlesex county': {'programmers': 81,    'salesmen': 62}}, 'new york': {'queens county': {'plumbers': 9,          'salesmen': 36},   'queens counyt': {}}}

说明:

Vividict每当访问键但缺少键时,我们都将提供类的另一个嵌套实例。(返回值分配很有用,因为它避免了我们在dict上额外调用getter,不幸的是,我们无法在设置它时返回它。)

请注意,这些与最受支持的答案具有相同的语义,但代码行的一半-nosklo的实现:

class AutoVivification(dict):    """Implementation of perl's autovivification feature."""    def __getitem__(self, item):        try: return dict.__getitem__(self, item)        except KeyError: value = self[item] = type(self)() return value

用法示范

下面只是一个示例,说明如何轻松地使用此dict即时创建嵌套的dict结构。这样可以快速创建一个层次结构树结构,如你所愿。

import pprintclass Vividict(dict):    def __missing__(self, key):        value = self[key] = type(self)()        return valued = Vividict()d['foo']['bar']d['foo']['baz']d['fizz']['buzz']d['primary']['secondary']['tertiary']['quaternary']pprint.pprint(d)

哪个输出:

{'fizz': {'buzz': {}}, 'foo': {'bar': {}, 'baz': {}}, 'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}

正如最后一行所示,它打印精美,便于人工检查。但是,如果要直观地检查数据,则实现

__missing__
将其类的新实例设置为键并返回该键是一个更好的解决方案。

对比其他替代方法:

dict.setdefault

尽管询问者认为这不干净,但我发现它比Vividict我自己更喜欢。

d = {} # or dict()for (state, county, occupation), number in data.items():    d.setdefault(state, {}).setdefault(county, {})[occupation] = number

现在:

>>> pprint.pprint(d, width=40){'new jersey': {'mercer county': {'plumbers': 3, 'programmers': 81},     'middlesex county': {'programmers': 81,    'salesmen': 62}}, 'new york': {'queens county': {'plumbers': 9,          'salesmen': 36}}}

拼写错误将严重失败,并且不会因错误信息而使我们的数据混乱:

>>> d['new york']['queens counyt']Traceback (most recent call last):  File "<stdin>", line 1, in <module>KeyError: 'queens counyt'

另外,我认为setdefault在循环中使用时效果很好,并且你不知道密钥要获得什么,但是重复使用变得很繁重,而且我认为没有人愿意遵守以下规定:

d = dict()d.setdefault('foo', {}).setdefault('bar', {})d.setdefault('foo', {}).setdefault('baz', {})d.setdefault('fizz', {}).setdefault('buzz', {})d.setdefault('primary', {}).setdefault('secondary', {}).setdefault('tertiary', {}).setdefault('quaternary', {})

另一个批评是setdefault是否需要使用新实例。但是,Python(或至少CPython)在处理未使用和未引用的新实例方面相当聪明,例如,它重用了内存中的位置:

>>> id({}), id({}), id({})(523575344, 523575344, 523575344)

自动更新的defaultdict

这是一个简洁的实现,不检查数据的脚本中的用法与实现一样有用__missing__:

from collections import defaultdictdef vivdict():    return defaultdict(vivdict)

但是,如果你需要检查数据,则以相同方式填充数据的自动复现defaultdict的结果如下所示:

>>> d = vivdict(); d['foo']['bar']; d['foo']['baz']; d['fizz']['buzz']; d['primary']['secondary']['tertiary']['quaternary']; import pprint; >>> pprint.pprint(d)defaultdict(<function vivdict at 0x17B01870>, {'foo': defaultdict(<function vivdict at 0x17B01870>, {'baz': defaultdict(<function vivdict at 0x17B01870>, {}), 'bar': defaultdict(<function vivdict at 0x17B01870>, {})}), 'primary': defaultdict(<function vivdict at 0x17B01870>, {'secondary': defaultdict(<function vivdict at 0x17B01870>, {'tertiary': defaultdict(<function vivdict at 0x17B01870>, {'quaternary': defaultdict(<function vivdict at 0x17B01870>, {})})})}), 'fizz': defaultdict(<function vivdict at 0x17B01870>, {'buzz': defaultdict(<function vivdict at 0x17B01870>, {})})})

此输出非常微不足道,并且结果非常不可读。通常给出的解决方案是递归转换回dict以进行手动检查。这个非平凡的解决方案留给读者练习。

性能

最后,让我们看一下性能。我要减去实例化的成本。

>>> import timeit>>> min(timeit.repeat(lambda: {}.setdefault('foo', {}))) - min(timeit.repeat(lambda: {}))0.13612580299377441>>> min(timeit.repeat(lambda: vivdict()['foo'])) - min(timeit.repeat(lambda: vivdict()))0.2936999797821045>>> min(timeit.repeat(lambda: Vividict()['foo'])) - min(timeit.repeat(lambda: Vividict()))0.5354437828063965>>> min(timeit.repeat(lambda: AutoVivification()['foo'])) - min(timeit.repeat(lambda: AutoVivification()))2.138362169265747

基于性能,

dict.setdefault
效果最佳。如果你关心执行速度,我强烈建议将其用于生产代码。

如果你需要将它用于交互式使用(也许是在IPython笔记本中),那么性能并不重要-在这种情况下,我会选择Vividict来确保输出的可读性。与AutoVivification对象(使用

__getitem__
而不是
__missing__
为此目的而使用)相比,它要优越得多。

结论

__missing__
在子类
dict
上实现以设置和返回新实例要比替代方法难一些,但具有以下优点:

  • 易于实例化
  • 简单数据填充
  • 轻松查看数据

并且因为它比修改不那么复杂且性能更高

__getitem__
,所以应该优先于该方法。

但是,它有缺点:

  • 错误的查询将自动失败。
  • 错误的查询将保留在词典中。

因此,我个人更喜欢setdefault其他解决方案,并且在每种情况下都需要这种行为。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/384137.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号