是否有内置 dict.get() 的递归版本?
Is there a recursive version of the dict.get() built-in?
我有一个嵌套的字典对象,我希望能够检索具有任意深度的键的值。我可以通过子类化 dict
:
来做到这一点
>>> class MyDict(dict):
... def recursive_get(self, *args, **kwargs):
... default = kwargs.get('default')
... cursor = self
... for a in args:
... if cursor is default: break
... cursor = cursor.get(a, default)
... return cursor
...
>>> d = MyDict(foo={'bar': 'baz'})
>>> d
{'foo': {'bar': 'baz'}}
>>> d.get('foo')
{'bar': 'baz'}
>>> d.recursive_get('foo')
{'bar': 'baz'}
>>> d.recursive_get('foo', 'bar')
'baz'
>>> d.recursive_get('bogus key', default='nonexistent key')
'nonexistent key'
但是,我不想必须继承 dict
才能获得此行为。是否有一些具有等效或相似行为的内置方法?如果没有,是否有提供此行为的任何标准或外部模块?
我目前正在使用 Python 2.7,不过我也很想知道 3.x 解决方案。
我知道 none。然而,你根本不需要子类化 dict,你可以只写一个函数,它接受一个字典、args 和 kwargs 并做同样的事情:
def recursive_get(d, *args, **kwargs):
default = kwargs.get('default')
cursor = d
for a in args:
if cursor is default: break
cursor = recursive_get(cursor, a, default)
return cursor
这样使用
recursive_get(d, 'foo', 'bar')
collections.default_dict 至少会为不存在的键提供默认值。
执行此操作的一种非常常见的模式是使用空字典作为默认值:
d.get('foo', {}).get('bar')
如果你有不止一对钥匙,你可以使用reduce
(注意在Python3中必须导入reduce
:from functools import reduce
)来申请多次操作
reduce(lambda c, k: c.get(k, {}), ['foo', 'bar'], d)
当然,你应该考虑把它包装成一个函数(或方法):
def recursive_get(d, *keys):
return reduce(lambda c, k: c.get(k, {}), keys, d)
考虑到默认关键字参数和元组分解的处理,您实际上可以在 Python 3 中非常巧妙地实现这一点:
In [1]: def recursive_get(d, *args, default=None):
...: if not args:
...: return d
...: key, *args = args
...: return recursive_get(d.get(key, default), *args, default=default)
...:
类似的代码也适用于 python 2,但您需要恢复使用 **kwargs
,就像您在示例中所做的那样。您还需要使用索引来分解 *args
.
无论如何,如果您要使函数递归,则不需要循环。
您可以看到上面的代码演示了与您现有方法相同的功能:
In [2]: d = {'foo': {'bar': 'baz'}}
In [3]: recursive_get(d, 'foo')
Out[3]: {'bar': 'baz'}
In [4]: recursive_get(d, 'foo', 'bar')
Out[4]: 'baz'
In [5]: recursive_get(d, 'bogus key', default='nonexistent key')
Out[5]: 'nonexistent key'
您可以使用 defaultdict 为您提供有关缺少键的空字典:
from collections import defaultdict
mydict = defaultdict(dict)
这只深入一层 - mydict[missingkey]
是一个空字典,mydict[missingkey][missing key]
是一个 KeyError。您可以根据需要添加尽可能多的级别,方法是将其包装在更多 defaultdict
中,例如 defaultdict(defaultdict(dict))
。您还可以将最里面的一个作为另一个 defaultdict,为您的用例提供一个合理的工厂函数,例如
mydict = defaultdict(defaultdict(lambda: 'big summer blowout'))
如果你需要它到达任意深度,你可以这样做:
def insanity():
return defaultdict(insanity)
print(insanity()[0][0][0][0])
是正确的,但求助于 lambda
函数,只有在中间密钥 不是 [=41= 时才需要避免 TypeError
] 存在。如果这不是问题,您可以直接使用 dict.get
:
from functools import reduce
def get_from_dict(dataDict, mapList):
"""Iterate nested dictionary"""
return reduce(dict.get, mapList, dataDict)
这是一个演示:
a = {'Alice': {'Car': {'Color': 'Blue'}}}
path = ['Alice', 'Car', 'Color']
get_from_dict(a, path) # 'Blue'
如果你希望比使用 lambda
更明确,同时仍然避免使用 TypeError
,你可以包含一个 try
/ except
子句:
def get_from_dict(dataDict, mapList):
"""Iterate nested dictionary"""
try:
return reduce(dict.get, mapList, dataDict)
except TypeError:
return None # or some other default value
最后,如果您希望在任何级别都不存在键时引发 KeyError
,请使用 operator.getitem
或 dict.__getitem__
:
from functools import reduce
from operator import getitem
def getitem_from_dict(dataDict, mapList):
"""Iterate nested dictionary"""
return reduce(getitem, mapList, dataDict)
# or reduce(dict.__getitem__, mapList, dataDict)
请注意,[]
是 __getitem__
方法的语法糖。因此,这恰恰与您通常访问字典值的方式有关。 operator
模块只是提供了一种更易读的方法来访问此方法。
迭代解
def deep_get(d:dict, keys, default=None, create=True):
if not keys:
return default
for key in keys[:-1]:
if key in d:
d = d[key]
elif create:
d[key] = {}
d = d[key]
else:
return default
key = keys[-1]
if key in d:
return d[key]
elif create:
d[key] = default
return default
def deep_set(d:dict, keys, value, create=True):
assert(keys)
for key in keys[:-1]:
if key in d:
d = d[key]
elif create:
d[key] = {}
d = d[key]
d[keys[-1]] = value
return value
我将在 Django 项目中使用如下一行对其进行测试:
keys = ('options', 'style', 'body', 'name')
val = deep_set(d, keys, deep_get(s, keys, 'dotted'))
我有一个嵌套的字典对象,我希望能够检索具有任意深度的键的值。我可以通过子类化 dict
:
>>> class MyDict(dict):
... def recursive_get(self, *args, **kwargs):
... default = kwargs.get('default')
... cursor = self
... for a in args:
... if cursor is default: break
... cursor = cursor.get(a, default)
... return cursor
...
>>> d = MyDict(foo={'bar': 'baz'})
>>> d
{'foo': {'bar': 'baz'}}
>>> d.get('foo')
{'bar': 'baz'}
>>> d.recursive_get('foo')
{'bar': 'baz'}
>>> d.recursive_get('foo', 'bar')
'baz'
>>> d.recursive_get('bogus key', default='nonexistent key')
'nonexistent key'
但是,我不想必须继承 dict
才能获得此行为。是否有一些具有等效或相似行为的内置方法?如果没有,是否有提供此行为的任何标准或外部模块?
我目前正在使用 Python 2.7,不过我也很想知道 3.x 解决方案。
我知道 none。然而,你根本不需要子类化 dict,你可以只写一个函数,它接受一个字典、args 和 kwargs 并做同样的事情:
def recursive_get(d, *args, **kwargs):
default = kwargs.get('default')
cursor = d
for a in args:
if cursor is default: break
cursor = recursive_get(cursor, a, default)
return cursor
这样使用
recursive_get(d, 'foo', 'bar')
collections.default_dict 至少会为不存在的键提供默认值。
执行此操作的一种非常常见的模式是使用空字典作为默认值:
d.get('foo', {}).get('bar')
如果你有不止一对钥匙,你可以使用reduce
(注意在Python3中必须导入reduce
:from functools import reduce
)来申请多次操作
reduce(lambda c, k: c.get(k, {}), ['foo', 'bar'], d)
当然,你应该考虑把它包装成一个函数(或方法):
def recursive_get(d, *keys):
return reduce(lambda c, k: c.get(k, {}), keys, d)
考虑到默认关键字参数和元组分解的处理,您实际上可以在 Python 3 中非常巧妙地实现这一点:
In [1]: def recursive_get(d, *args, default=None):
...: if not args:
...: return d
...: key, *args = args
...: return recursive_get(d.get(key, default), *args, default=default)
...:
类似的代码也适用于 python 2,但您需要恢复使用 **kwargs
,就像您在示例中所做的那样。您还需要使用索引来分解 *args
.
无论如何,如果您要使函数递归,则不需要循环。
您可以看到上面的代码演示了与您现有方法相同的功能:
In [2]: d = {'foo': {'bar': 'baz'}}
In [3]: recursive_get(d, 'foo')
Out[3]: {'bar': 'baz'}
In [4]: recursive_get(d, 'foo', 'bar')
Out[4]: 'baz'
In [5]: recursive_get(d, 'bogus key', default='nonexistent key')
Out[5]: 'nonexistent key'
您可以使用 defaultdict 为您提供有关缺少键的空字典:
from collections import defaultdict
mydict = defaultdict(dict)
这只深入一层 - mydict[missingkey]
是一个空字典,mydict[missingkey][missing key]
是一个 KeyError。您可以根据需要添加尽可能多的级别,方法是将其包装在更多 defaultdict
中,例如 defaultdict(defaultdict(dict))
。您还可以将最里面的一个作为另一个 defaultdict,为您的用例提供一个合理的工厂函数,例如
mydict = defaultdict(defaultdict(lambda: 'big summer blowout'))
如果你需要它到达任意深度,你可以这样做:
def insanity():
return defaultdict(insanity)
print(insanity()[0][0][0][0])
lambda
函数,只有在中间密钥 不是 [=41= 时才需要避免 TypeError
] 存在。如果这不是问题,您可以直接使用 dict.get
:
from functools import reduce
def get_from_dict(dataDict, mapList):
"""Iterate nested dictionary"""
return reduce(dict.get, mapList, dataDict)
这是一个演示:
a = {'Alice': {'Car': {'Color': 'Blue'}}}
path = ['Alice', 'Car', 'Color']
get_from_dict(a, path) # 'Blue'
如果你希望比使用 lambda
更明确,同时仍然避免使用 TypeError
,你可以包含一个 try
/ except
子句:
def get_from_dict(dataDict, mapList):
"""Iterate nested dictionary"""
try:
return reduce(dict.get, mapList, dataDict)
except TypeError:
return None # or some other default value
最后,如果您希望在任何级别都不存在键时引发 KeyError
,请使用 operator.getitem
或 dict.__getitem__
:
from functools import reduce
from operator import getitem
def getitem_from_dict(dataDict, mapList):
"""Iterate nested dictionary"""
return reduce(getitem, mapList, dataDict)
# or reduce(dict.__getitem__, mapList, dataDict)
请注意,[]
是 __getitem__
方法的语法糖。因此,这恰恰与您通常访问字典值的方式有关。 operator
模块只是提供了一种更易读的方法来访问此方法。
迭代解
def deep_get(d:dict, keys, default=None, create=True):
if not keys:
return default
for key in keys[:-1]:
if key in d:
d = d[key]
elif create:
d[key] = {}
d = d[key]
else:
return default
key = keys[-1]
if key in d:
return d[key]
elif create:
d[key] = default
return default
def deep_set(d:dict, keys, value, create=True):
assert(keys)
for key in keys[:-1]:
if key in d:
d = d[key]
elif create:
d[key] = {}
d = d[key]
d[keys[-1]] = value
return value
我将在 Django 项目中使用如下一行对其进行测试:
keys = ('options', 'style', 'body', 'name')
val = deep_set(d, keys, deep_get(s, keys, 'dotted'))