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


201

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

{'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()手动设置。

Answers:


178

在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 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,setdefault都需要一个新实例。但是,Python(或至少CPython)在处理未使用和未引用的新实例方面相当聪明,例如,它重用了内存中的位置:

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

自动更新的defaultdict

这是一个简洁的实现,不检查数据的脚本中的用法与实现一样有用__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>, {})})})

此输出非常微不足道,并且结果非常不可读。通常给出的解决方案是将其递归转换回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其他解决方案,并且在每种情况下都需要这种行为。


很好的答案!有什么方法可以指定一个有限的深度和一个叶子类型Vividict吗?例如3,如果list要使用列表中的命令,则可以使用来填充d['primary']['secondary']['tertiary'].append(element)。我可以为每个深度定义3个不同的类,但是我很想找到一个更清洁的解决方案。
埃里克·杜米尼尔

@EricDuminil- d['primary']['secondary'].setdefault('tertiary', []).append('element')?? 感谢您的赞美,但是老实说-我从没真正使用过__missing__-我总是使用setdefault。我可能应该更新我的结论/简介...
亚伦·霍尔

@AaronHall正确的行为是,如果需要,代码应创建一个dict。在这种情况下,将覆盖先前分配的值。
nehem

@AaronHall The bad lookup will remain in the dictionary.当我考虑使用此解决方案时,您还能帮我理解什么意思吗?非常感激。Thx
nehem

@AaronHall setdefault当它嵌套两个以上级别的深度时,它的问题将失败。看起来Python中没有任何一种结构可以像所描述的那样提供真正的活力。我必须解决两种陈述方法,一种用于get_nested&一种,set_nested用于接受dict和嵌套属性列表的引用。
nehem

188
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时遇到这个问题吗?stackoverflow.com/questions/54622935/…–
杰森(Jason)

@jason pickle在python版本之间非常糟糕。避免使用它来存储要保留的数据。仅将其用于可随意转储和重新生成的缓存和内容。不作为长期存储或序列化方法。
nosklo

您用什么来存储这些对象?我的自动生存对象仅包含熊猫数据框和字符串。
杰森

@jason根据数据,我喜欢使用JSON,csv文件甚至sqlite数据库来存储它。
nosklo

30

只是因为我还没有看到这么小的一个,这是一个像您想嵌套的字典一样,没有汗水:

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

2
@wberry:实际上,您需要的只是yodict = lambda: defaultdict(yodict)
martineau 2013年

1
接受的版本是的子类dict,因此要完全等效,我们需要x = Vdict(a=1, b=2)工作。
wberry

@wberry:不管接受的答案是什么dict,OP都不是要求的子类,OP仅要求实现“最佳方式”来实现它们-而且,它不/不应该无论如何,Python都如此重要。
martineau 2014年

24

您可以创建一个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(...)


4
输入大量深度嵌套的数据(以及配置文件,数据库原型等)时,绝对是我的首选。如果OP不需要多余的文件,只需在某些文件中使用常规Python字符串,然后使用YAML进行解析即可。
kmelvn

创建YAML字符串的要点:与重复使用“ tempfile”模块相比,这是一种更干净的方法。
皮特

18

由于您具有星形模式设计,因此您可能希望使其结构更像关系表,而不像字典。

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开销的情况下,创建类似数据仓库的设计可以走很长一段路。


14

如果嵌套级别的数量很少,那么我可以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()!
John Fouhy

8

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

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)

7

我觉得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我得到“用于增值分配的非法表达式”
dfrankow

6

正如其他人所建议的,关系数据库对您可能更有用。您可以使用内存中的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"'))

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


5

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)

1
这是最接近我想要的答案。但理想情况下,应该有各种各样的辅助函数,例如walk_keys()或类似的函数。我很惊讶标准库中没有任何东西可以做到这一点。
YGA

4

至于“令人讨厌的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


4

defaultdict() 是你的朋友!

对于二维字典,您可以执行以下操作:

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

有关更多尺寸,您可以:

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

此答案最多仅适用于三个级别。对于任意级别,请考虑以下答案
Acumenus

3

为了方便地迭代嵌套字典,为什么不编写一个简单的生成器呢?

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

我在这里对数据结构的含义进行假设,但是应该很容易根据实际需要进行调整。


2

我喜欢的一类包装这和实施的想法__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

3
@YGA您可能是对的,但是考虑实现这种小型语言很有趣。
亚伦·曼帕

1

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


1
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)查询时返回字典,否则返回单个值。


为什么要返回列表?似乎它应该返回一个字典(这样您就知道每个数字代表什么)或一个和(因为这样就可以对列表进行真正的处理了)。
本·布兰克

0

我也有类似的事情。我有很多情况下会这样做:

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

更好?我认同。


0

您可以在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})})})

0

我曾经使用此功能。其安全,快速,易于维护。

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
>>>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.