实现嵌套字典的最佳方法是啥?

Posted

技术标签:

【中文标题】实现嵌套字典的最佳方法是啥?【英文标题】:What is the best way to implement nested dictionaries?实现嵌套字典的最佳方法是什么? 【发布时间】:2010-10-12 17:42:51 【问题描述】:

我有一个基本上相当于嵌套字典的数据结构。假设它看起来像这样:

'new jersey': 'mercer county': 'plumbers': 3,
                                  'programmers': 81,
                'middlesex county': 'programmers': 81,
                                     'salesmen': 62,
 'new york': 'queens county': 'plumbers': 9,
                                'salesmen': 36

现在,维护和创建它非常痛苦;每次我有一个新的州/县/专业时,我都必须通过令人讨厌的 try/catch 块创建下层词典。此外,如果我想遍历所有值,我必须创建烦人的嵌套迭代器。

我也可以使用元组作为键,例如:

('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

这使得对值的迭代变得非常简单和自然,但在语法上执行聚合和查看字典的子集(例如,如果我只想逐个状态)会更加痛苦。

基本上,有时我想将嵌套字典视为平面字典,有时我想将其视为复杂的层次结构。我可以将这一切都包装在一个类中,但似乎有人可能已经这样做了。或者,似乎有一些非常优雅的句法结构可以做到这一点。

我怎样才能做得更好?

附录:我知道setdefault(),但它并不能真正实现简洁的语法。此外,您创建的每个子词典仍然需要手动设置 setdefault()

【问题讨论】:

【参考方案1】:

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

这是一个坏主意,不要这样做。相反,请使用常规字典并使用dict.setdefault where apropos,因此当在正常使用下缺少键时,您会得到预期的KeyError。如果你坚持要采取这种行为,这里是如何在脚下开枪:

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

这种方法从 Python 2.5 开始就可以使用 (and documented),并且(对我来说特别有价值)它的打印效果就像一个普通的字典,而不是自动生成的 defaultdict 的丑陋打印: p>

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 pprint

class Vividict(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

d = 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)

一个自动激活的默认字典

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

from collections import defaultdict

def 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>, )))

这个输出很不雅,结果很不可读。通常给出的解决方案是递归地转换回字典以进行手动检查。这个重要的解决方案留给读者作为练习。

性能

最后,让我们看看性能。我正在减去实例化的成本。

>>> 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__)相比,它要优越得多。

结论

在子类dict 上实现__missing__ 以设置和返回新实例比其他方法稍微困难一些,但具有以下优点

简单的实例化 轻松的数据填充 轻松查看数据

而且由于它比修改__getitem__ 更简单,性能更高,因此应该首选该方法。

尽管如此,它也有缺点:

错误的查找会静默失败。 错误的查找将保留在字典中。

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

【讨论】:

优秀的答案!有没有办法为Vividict 指定有限深度和叶子类型?例如。 3list 用于可以填充 d['primary']['secondary']['tertiary'].append(element) 的列表字典的字典。我可以为每个深度定义 3 个不同的类,但我很想找到一个更简洁的解决方案。 @EricDuminil d['primary']['secondary'].setdefault('tertiary', []).append('element') - ??感谢您的赞美,但老实说 - 我从未真正使用过__missing__ - 我总是使用setdefault。我可能应该更新我的结论/介绍...... @AaronHall 正确的行为是代码应该在需要时创建一个字典。在这种情况下,通过覆盖先前分配的值。 @AaronHall 在我考虑使用此解决方案时,您还能帮我理解The bad lookup will remain in the dictionary. 的含义吗?非常感激。谢谢 @AaronHall 当它嵌套超过两个深度级别时,它的问题将失败setdefault。看起来 Python 中的任何结构都不能像描述的那样提供真正的活力。我不得不接受两种声明方法,一种用于get_nested,另一种用于set_nested,它们接受字典和嵌套属性列表的引用。【参考方案2】:
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

测试:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

输出:

1: 2: 'test': 6, 3: 4, 3: 3: 5

【讨论】:

有人在迁移到 python 3.x 时遇到这个问题吗? ***.com/questions/54622935/… @jason pickle 在 python 版本之间很糟糕。避免使用它来存储您想要保留的数据。仅将它用于缓存和您可以随意转储和重新生成的东西。不作为长期存储或序列化方法。 你用什么来存储这些对象?我的 autovivification 对象只包含 pandas 数据框和字符串。 @jason 根据数据的不同,我喜欢使用 JSON、csv 文件,甚至是 sqlite 数据库来存储它。【参考方案3】:

只是因为我还没有见过这么小的一个,这里有一个可以随意嵌套的字典,没有汗水:

# yo dawg, i heard you liked dicts                                                                      
def yodict():
    return defaultdict(yodict)

【讨论】:

@wberry:其实你只需要yodict = lambda: defaultdict(yodict) 接受的版本是dict 的子类,所以要完全等效,我们需要x = Vdict(a=1, b=2) 才能工作。 @wberry:不管接受的答案是什么,作为 dict 的子类并不是 OP 规定的要求,他们只要求“最佳方式”来实现它们——此外,无论如何,它在 Python 中并不/不应该那么重要。【参考方案4】:

您可以创建一个 YAML 文件并使用 PyYaml 读取它。

第 1 步:创建 YAML 文件“employment.yml”:

new jersey:
  mercer county:
    pumbers: 3
    programmers: 81
  middlesex county:
    salesmen: 62
    programmers: 81
new york:
  queens county:
    plumbers: 9
    salesmen: 36

第 2 步:用 Python 阅读

import yaml
file_handle = open("employment.yml")
my_shnazzy_dictionary = yaml.safe_load(file_handle)
file_handle.close()

现在my_shnazzy_dictionary 拥有你所有的价值观。如果您需要即时执行此操作,您可以将 YAML 创建为字符串并将其提供给 yaml.safe_load(...)

【讨论】:

YAML 绝对是我输入大量深度嵌套数据(以及配置文件、数据库模型等)的选择。如果 OP 不想要额外的文件,只需在某个文件中使用常规 Python 字符串并使用 YAML 解析它。 创建 YAML 字符串的要点:这将比重复使用“tempfile”模块更简洁。【参考方案5】:

由于您有星型模式设计,您可能希望将其构建得更像关系表而不是字典。

import collections

class Jobs( object ):
    def __init__( self, state, county, title, count ):
        self.state= state
        self.count= county
        self.title= title
        self.count= count

facts = [
    Jobs( 'new jersey', 'mercer county', 'plumbers', 3 ),
    ...

def groupBy( facts, name ):
    total= collections.defaultdict( int )
    for f in facts:
        key= getattr( f, name )
        total[key] += f.count

这样的事情可以大大有助于创建类似数据仓库的设计,而无需 SQL 开销。

【讨论】:

【参考方案6】:

如果嵌套层数很少,我使用collections.defaultdict

from collections import defaultdict

def nested_dict_factory(): 
  return defaultdict(int)
def nested_dict_factory2(): 
  return defaultdict(nested_dict_factory)
db = defaultdict(nested_dict_factory2)

db['new jersey']['mercer county']['plumbers'] = 3
db['new jersey']['mercer county']['programmers'] = 81

像这样使用defaultdict可以避免很多乱七八糟的setdefault()get()

【讨论】:

+1:defaultdict 是我一直以来最喜欢的 python 新增功能之一。没有更多的 .setdefault()!【参考方案7】:

这是一个返回任意深度的嵌套字典的函数:

from collections import defaultdict
def make_dict():
    return defaultdict(make_dict)

像这样使用它:

d=defaultdict(make_dict)
d["food"]["meat"]="beef"
d["food"]["veggie"]="corn"
d["food"]["sweets"]="ice cream"
d["animal"]["pet"]["dog"]="collie"
d["animal"]["pet"]["cat"]="tabby"
d["animal"]["farm animal"]="chicken"

用这样的方式遍历所有内容:

def iter_all(d,depth=1):
    for k,v in d.iteritems():
        print "-"*depth,k
        if type(v) is defaultdict:
            iter_all(v,depth+1)
        else:
            print "-"*(depth+1),v

iter_all(d)

打印出来:

- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken

您最终可能希望这样做,以便不能将新项目添加到字典中。很容易递归地将所有这些defaultdicts 转换为普通的dicts。

def dictify(d):
    for k,v in d.iteritems():
        if isinstance(v,defaultdict):
            d[k] = dictify(v)
    return dict(d)

【讨论】:

【参考方案8】:

正如其他人所建议的,关系数据库可能对您更有用。您可以使用内存中的 sqlite3 数据库作为数据结构来创建表,然后查询它们。

import sqlite3

c = sqlite3.Connection(':memory:')
c.execute('CREATE TABLE jobs (state, county, title, count)')

c.executemany('insert into jobs values (?, ?, ?, ?)', [
    ('New Jersey', 'Mercer County',    'Programmers', 81),
    ('New Jersey', 'Mercer County',    'Plumbers',     3),
    ('New Jersey', 'Middlesex County', 'Programmers', 81),
    ('New Jersey', 'Middlesex County', 'Salesmen',    62),
    ('New York',   'Queens County',    'Salesmen',    36),
    ('New York',   'Queens County',    'Plumbers',     9),
])

# some example queries
print list(c.execute('SELECT * FROM jobs WHERE county = "Queens County"'))
print list(c.execute('SELECT SUM(count) FROM jobs WHERE title = "Programmers"'))

这只是一个简单的例子。您可以为州、县和职位定义单独的表格。

【讨论】:

【参考方案9】:

我觉得setdefault 很有用;它检查是否存在密钥,如果不存在则添加它:

d = 
d.setdefault('new jersey', ).setdefault('mercer county', )['plumbers'] = 3

setdefault 始终返回相关键,因此您实际上是在原地更新 'd' 的值。

在迭代方面,如果 Python 中不存在生成器,我相信您可以轻松编写生成器:

def iterateStates(d):
    # Let's count up the total number of "plumbers" / "dentists" / etc.
    # across all counties and states
    job_totals = 

    # I guess this is the annoying nested stuff you were talking about?
    for (state, counties) in d.iteritems():
        for (county, jobs) in counties.iteritems():
            for (job, num) in jobs.iteritems():
                # If job isn't already in job_totals, default it to zero
                job_totals[job] = job_totals.get(job, 0) + num

    # Now return an iterator of (job, number) tuples
    return job_totals.iteritems()

# Display all jobs
for (job, num) in iterateStates(d):
    print "There are %d %s in total" % (job, num)

【讨论】:

我喜欢这个解决方案,但是当我尝试时: count.setdefault(a, ).setdefault(b, ).setdefault(c, 0) += 1 我得到“非法表达式扩充作业”【参考方案10】:

collections.defaultdict 可以被子类化以制作嵌套字典。然后向该类添加任何有用的迭代方法。

>>> from collections import defaultdict
>>> class nesteddict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, nesteddict)
    def walk(self):
        for key, value in self.iteritems():
            if isinstance(value, nesteddict):
                for tup in value.walk():
                    yield (key,) + tup
            else:
                yield key, value


>>> nd = nesteddict()
>>> nd['new jersey']['mercer county']['plumbers'] = 3
>>> nd['new jersey']['mercer county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['salesmen'] = 62
>>> nd['new york']['queens county']['plumbers'] = 9
>>> nd['new york']['queens county']['salesmen'] = 36
>>> for tup in nd.walk():
    print tup


('new jersey', 'mercer county', 'programmers', 81)
('new jersey', 'mercer county', 'plumbers', 3)
('new jersey', 'middlesex county', 'programmers', 81)
('new jersey', 'middlesex county', 'salesmen', 62)
('new york', 'queens county', 'salesmen', 36)
('new york', 'queens county', 'plumbers', 9)

【讨论】:

这是最接近我所寻找的答案。但理想情况下会有各种各样的辅助函数,例如walk_keys() 之类的。我很惊讶标准库中没有任何东西可以做到这一点。【参考方案11】:

至于“讨厌的 try/catch 块”:

d = 
d.setdefault('key',).setdefault('inner key',)['inner inner key'] = 'value'
print d

产量

'key': 'inner key': 'inner inner key': 'value'

您可以使用它从平面字典格式转换为结构化格式:

fd = ('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

for (k1,k2,k3), v in fd.iteritems():
    d.setdefault(k1, ).setdefault(k2, )[k3] = v

【讨论】:

【参考方案12】:

您可以使用 Addict:https://github.com/mewwts/addict

>>> from addict import Dict
>>> my_new_shiny_dict = Dict()
>>> my_new_shiny_dict.a.b.c.d.e = 2
>>> my_new_shiny_dict
'a': 'b': 'c': 'd': 'e': 2

【讨论】:

【参考方案13】:

defaultdict()是你的朋友!

对于二维字典,您可以这样做:

d = defaultdict(defaultdict)
d[1][2] = 3

对于更多维度,您可以:

d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4

【讨论】:

这个答案最多只适用于三个级别。对于任意级别,请考虑this answer。【参考方案14】:

为了轻松迭代嵌套字典,为什么不写一个简单的生成器?

def each_job(my_dict):
    for state, a in my_dict.items():
        for county, b in a.items():
            for job, value in b.items():
                yield 
                    'state'  : state,
                    'county' : county,
                    'job'    : job,
                    'value'  : value
                

那么,如果你有编译好的嵌套字典,那么迭代它就变得简单了:

for r in each_job(my_dict):
    print "There are %d %s in %s, %s" % (r['value'], r['job'], r['county'], r['state'])

显然,您的生成器可以生成对您有用的任何格式的数据。

为什么要使用 try catch 块来读取树?在尝试检索密钥之前查询字典中是否存在密钥很容易(并且可能更安全)。使用保护子句的函数可能如下所示:

if not my_dict.has_key('new jersey'):
    return False

nj_dict = my_dict['new jersey']
...

或者,一种可能有点冗长的方法是使用 get 方法:

value = my_dict.get('new jersey', ).get('middlesex county', ).get('salesmen', 0)

但对于更简洁的方式,您可能希望使用 collections.defaultdict,它是自 python 2.5 以来标准库的一部分。

import collections

def state_struct(): return collections.defaultdict(county_struct)
def county_struct(): return collections.defaultdict(job_struct)
def job_struct(): return 0

my_dict = collections.defaultdict(state_struct)

print my_dict['new jersey']['middlesex county']['salesmen']

我在这里对您的数据结构的含义做出假设,但它应该很容易根据您实际想要做的事情进行调整。

【讨论】:

【参考方案15】:

我喜欢将其包装在一个类中并实现 __getitem____setitem__ 以便它们实现一种简单的查询语言的想法:

>>> d['new jersey/mercer county/plumbers'] = 3
>>> d['new jersey/mercer county/programmers'] = 81
>>> d['new jersey/mercer county/programmers']
81
>>> d['new jersey/mercer country']
<view which implicitly adds 'new jersey/mercer county' to queries/mutations>

如果你想变得花哨,你也可以实现类似的东西:

>>> d['*/*/programmers']
<view which would contain 'programmers' entries>

但大多数情况下,我认为实现这样的事情会非常有趣:D

【讨论】:

我认为这是个坏主意——你永远无法预测键的语法。您仍然会覆盖 getitemsetitem 但让它们采用元组。 @YGA 你可能是对的,但是考虑实现这样的迷你语言很有趣。【参考方案16】:

除非您的数据集非常小,否则您可能需要考虑使用关系数据库。它将完全满足您的需求:轻松添加计数、选择计数子集,甚至按州、县、职业或这些的任意组合汇总计数。

【讨论】:

【参考方案17】:
class JobDb(object):
    def __init__(self):
        self.data = []
        self.all = set()
        self.free = []
        self.index1 = 
        self.index2 = 
        self.index3 = 

    def _indices(self,(key1,key2,key3)):
        indices = self.all.copy()
        wild = False
        for index,key in ((self.index1,key1),(self.index2,key2),
                                             (self.index3,key3)):
            if key is not None:
                indices &= index.setdefault(key,set())
            else:
                wild = True
        return indices, wild

    def __getitem__(self,key):
        indices, wild = self._indices(key)
        if wild:
            return dict(self.data[i] for i in indices)
        else:
            values = [self.data[i][-1] for i in indices]
            if values:
                return values[0]

    def __setitem__(self,key,value):
        indices, wild = self._indices(key)
        if indices:
            for i in indices:
                self.data[i] = key,value
        elif wild:
            raise KeyError(k)
        else:
            if self.free:
                index = self.free.pop(0)
                self.data[index] = key,value
            else:
                index = len(self.data)
                self.data.append((key,value))
                self.all.add(index)
            self.index1.setdefault(key[0],set()).add(index)
            self.index2.setdefault(key[1],set()).add(index)
            self.index3.setdefault(key[2],set()).add(index)

    def __delitem__(self,key):
        indices,wild = self._indices(key)
        if not indices:
            raise KeyError
        self.index1[key[0]] -= indices
        self.index2[key[1]] -= indices
        self.index3[key[2]] -= indices
        self.all -= indices
        for i in indices:
            self.data[i] = None
        self.free.extend(indices)

    def __len__(self):
        return len(self.all)

    def __iter__(self):
        for key,value in self.data:
            yield key

例子:

>>> db = JobDb()
>>> db['new jersey', 'mercer county', 'plumbers'] = 3
>>> db['new jersey', 'mercer county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'salesmen'] = 62
>>> db['new york', 'queens county', 'plumbers'] = 9
>>> db['new york', 'queens county', 'salesmen'] = 36

>>> db['new york', None, None]
('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36

>>> db[None, None, 'plumbers']
('new jersey', 'mercer county', 'plumbers'): 3,
 ('new york', 'queens county', 'plumbers'): 9

>>> db['new jersey', 'mercer county', None]
('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81

>>> db['new jersey', 'middlesex county', 'programmers']
81

>>>

编辑:现在使用通配符 (None) 查询时返回字典,否则返回单个值。

【讨论】:

为什么要返回列表?似乎它应该返回一个字典(所以你知道每个数字代表什么)或一个总和(因为这就是你真正可以用列表做的所有事情)。【参考方案18】:

我也有类似的事情要做。我有很多这样的案例:

thedict = 
for item in ('foo', 'bar', 'baz'):
  mydict = thedict.get(item, )
  mydict = get_value_for(item)
  thedict[item] = mydict

但是要深入很多层次。关键是“.get(item, )”,因为如果还没有字典,它将创建另一个字典。与此同时,我一直在想办法应对 这个更好。现在有很多

value = mydict.get('foo', ).get('bar', ).get('baz', 0)

所以,我做了:

def dictgetter(thedict, default, *args):
  totalargs = len(args)
  for i,arg in enumerate(args):
    if i+1 == totalargs:
      thedict = thedict.get(arg, default)
    else:
      thedict = thedict.get(arg, )
  return thedict

如果这样做,效果相同:

value = dictgetter(mydict, 0, 'foo', 'bar', 'baz')

更好?我想是的。

【讨论】:

【参考方案19】:

您可以在 lambdas 和 defaultdict 中使用递归,无需定义名称:

a = defaultdict((lambda f: f(f))(lambda g: lambda:defaultdict(g(g))))

这是一个例子:

>>> a['new jersey']['mercer county']['plumbers']=3
>>> a['new jersey']['middlesex county']['programmers']=81
>>> a['new jersey']['mercer county']['programmers']=81
>>> a['new jersey']['middlesex county']['salesmen']=62
>>> a
defaultdict(<function __main__.<lambda>>,
        'new jersey': defaultdict(<function __main__.<lambda>>,
                     'mercer county': defaultdict(<function __main__.<lambda>>,
                                  'plumbers': 3, 'programmers': 81),
                      'middlesex county': defaultdict(<function __main__.<lambda>>,
                                  'programmers': 81, 'salesmen': 62)))

【讨论】:

【参考方案20】:

我曾经使用过这个功能。它安全、快速、易于维护。

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

例子:

>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
...     return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = 'person':'name':'first':'John'
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>

【讨论】:

【参考方案21】:

对于以下(从上面复制)有一种方法可以实现附加功能。我正在尝试使用嵌套字典将值存储为数组。

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

我目前的实现如下:

totalGeneHash=Vividict()
        
for keys in GenHash:
    for second in GenHash[keys]:
        if keys in sampleHash:
            total_val = GenHash[keys][second]
                totalGeneHash[gene][keys].append(total_val)
This is the error I get: AttributeError: 'Vividict' object has no attribute 'append'

【讨论】:

以上是关于实现嵌套字典的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中创建嵌套列表的最佳方法是啥?

打破 JavaScript 中嵌套循环的最佳方法是啥?

用pydantic指定嵌套字典的最佳方法?

从嵌套的 javascript 对象中删除属性的最佳方法是啥?

在python中按值删除字典项的最佳方法是啥? [复制]

从字典列表创建 Pandas MultiIndex 的最佳方法是啥?