在给定索引和值列表的嵌套 Python 字典中设置值

问题描述 投票:0回答:11

我正在尝试以编程方式在字典中设置一个值,可能是嵌套的,给定索引列表和值。

例如,假设我的索引列表是:

['person', 'address', 'city']

其值为

'New York'

我想要一个像这样的字典对象:

{ 'Person': { 'address': { 'city': 'New York' } }

基本上,列表代表嵌套字典的“路径”。

我认为我可以构建字典本身,但我遇到的问题是如何设置值。显然,如果我只是手动为此编写代码,那就是:

dict['Person']['address']['city'] = 'New York'

但是,如果我只有索引和值的列表,如何索引到字典并以编程方式设置类似的值?

Python

python list dictionary
11个回答
82
投票

这样的事情可能会有所帮助:

def nested_set(dic, keys, value):
    for key in keys[:-1]:
        dic = dic.setdefault(key, {})
    dic[keys[-1]] = value

你可以这样使用它:

>>> d = {}
>>> nested_set(d, ['person', 'address', 'city'], 'New York')
>>> d
{'person': {'address': {'city': 'New York'}}}

8
投票

我从Bakuriu的答案中获得了扩展代码的自由。因此,对此的赞成是可选的,因为他的代码本身就是一个机智的解决方案,这是我不会想到的。

def nested_set(dic, keys, value, create_missing=True):
    d = dic
    for key in keys[:-1]:
        if key in d:
            d = d[key]
        elif create_missing:
            d = d.setdefault(key, {})
        else:
            return dic
    if keys[-1] in d or create_missing:
        d[keys[-1]] = value
    return dic

create_missing
设置为 True 时,您确保只设置已经存在的值:

# Trying to set the value of a non-existing key won't 
# create it if "create_missing" is set to False

print(nested_set({"A": {"B": 1}}, ["A", "8"], 2, False))

# >>> {'A': {'B': 1}}


# The key/value pair will be created if "create_missing"
# is set to True 

print(nested_set({"A": {"B": 1}}, ["A", "8"], 2, True))

# >>> {'A': {'B': 1, '8': 2}}


# If a key exists its value will be overwritten.
# Here it's the sub-key "B", addressed by the path "A.B",
# build from the array in parameter "keys".

print(nested_set({"A": {"B": 1}}, ["A", "B"], 2))

# >>> {'A': {'B': 2}}

4
投票

这是另一种选择:

from collections import defaultdict
recursivedict = lambda: defaultdict(recursivedict)
mydict = recursivedict()

我最初是从这里得到的:设置嵌套字典值并创建中间键

如果你问我的话,这是相当聪明和优雅的。


3
投票

首先,您可能想查看 setdefault

作为一个函数,我将其写为

def get_leaf_dict(dct, key_list):
    res=dct
    for key in key_list:
        res=res.setdefault(key, {})
    return res

这将用作:

get_leaf_dict( dict, ['Person', 'address', 'city']) = 'New York'

这可以通过错误处理等来清理。另外使用

*args
而不是单个键列表参数可能会更好;但想法是 您可以迭代键,在每个级别提取适当的字典。


2
投票

这是我的简单解决方案:只需写

terms = ['person', 'address', 'city'] 
result = nested_dict(3, str)
result[terms] = 'New York'  # as easy as it can be

你甚至可以这样做:

terms = ['John', 'Tinkoff', '1094535332']  # account in Tinkoff Bank
result = nested_dict(3, float)
result[terms] += 2375.30

现在后台:

from collections import defaultdict


class nesteddict(defaultdict):
    def __getitem__(self, key):
        if isinstance(key, list):
            d = self
            for i in key:
                d = defaultdict.__getitem__(d, i)
            return d
        else:
            return defaultdict.__getitem__(self, key)
    def __setitem__(self, key, value):
        if isinstance(key, list):
            d = self[key[:-1]]
            defaultdict.__setitem__(d, key[-1], value)
        else:
            defaultdict.__setitem__(self, key, value)


def nested_dict(n, type):
    if n == 1:
        return nesteddict(type)
    else:
        return nesteddict(lambda: nested_dict(n-1, type))

2
投票

使用这对方法

def gattr(d, *attrs):
    """
    This method receives a dict and list of attributes to return the innermost value of the give dict
    """
    try:
        for at in attrs:
            d = d[at]
        return d
    except:
        return None


def sattr(d, *attrs):
    """
    Adds "val" to dict in the hierarchy mentioned via *attrs
    For ex:
    sattr(animals, "cat", "leg","fingers", 4) is equivalent to animals["cat"]["leg"]["fingers"]=4
    This method creates necessary objects until it reaches the final depth
    This behaviour is also known as autovivification and plenty of implementation are around
    This implementation addresses the corner case of replacing existing primitives
    https://gist.github.com/hrldcpr/2012250#gistcomment-1779319
    """
    for attr in attrs[:-2]:
        # If such key is not found or the value is primitive supply an empty dict
        if d.get(attr) is None or isinstance(d.get(attr), dict):
            d[attr] = {}
        d = d[attr]
    d[attrs[-2]] = attrs[-1]

2
投票

Python 3 的 dotty_dict 库可以做到这一点。请参阅文档 Dotty Dict 了解更多详情。

from dotty_dict import dotty

dot = dotty()
string = '.'.join(['person', 'address', 'city'])
dot[string] = 'New York'

print(dot)

输出:

{'person': {'address': {'city': 'New York'}}}

1
投票

这是 Bakuriu 的答案的一个变体,它不依赖于单独的函数:

keys = ['Person', 'address', 'city']
value = 'New York'

nested_dict = {}

# Build nested dictionary up until 2nd to last key
# (Effectively nested_dict['Person']['address'] = {})
sub_dict = nested_dict
for key_ind, key in enumerate(keys[:-1]):
    if not key_ind:
        # Point to newly added piece of dictionary
        sub_dict = nested_dict.setdefault(key, {})
    else:
        # Point to newly added piece of sub-dictionary
        # that is also added to original dictionary
        sub_dict = sub_dict.setdefault(key, {})
# Add value to last key of nested structure of keys
# (Effectively nested_dict['Person']['address']['city'] = value)
sub_dict[keys[-1]] = value

print(nested_dict)

>>> {'Person': {'address': {'city': 'New York'}}}

0
投票

这是递归函数的一个非常好的用例。所以你可以做这样的事情:

def parse(l: list, v: str) -> dict:
    copy = dict()
    k, *s = l
    if len(s) > 0:
        copy[k] = parse(s, v)
    else:
        copy[k] = v
    return copy

这有效地弹出了传递列表

l
的第一个值作为我们初始化的字典
copy
的键,然后通过相同的函数运行剩余的列表,在that键下创建一个新键,直到没有任何内容留在列表中,然后将最后一个值分配给
v
参数。


0
投票

您可以编写自己的帮助器类以使您的代码清晰。
dict_helper.py:

from collections import UserDict

# reference: https://realpython.com/inherit-python-dict/

class DictHelper(UserDict):
    """
    reference: https://stackoverflow.com/questions/43491287/elegant-way-to-check-if-a-nested-key-exists-in-a-dict
    """

    def has_key(self, path: str | list):
        keys = path.split('.') if isinstance(path, str) else path

        val = self.data
        for key in keys:
            if not key in val:
                return False
            else:
                val = val[key]
        return True

    # Throwing in this approach for nested get for the heck of it...
    def get_key(self, path: str | list, default=None):
        keys = path.split('.') if isinstance(path, str) else path

        val = self.data
        for key in keys:
            if key in val:
                val = val[key]
            else:
                return default
        return val

    def set_key(self, path: str | list, value):
        keys = path.split('.') if isinstance(path, str) else path

        val = self.data
        for key in keys[:-1]:
            val.setdefault(key, {})
            val = val[key]
        val[keys[-1]] = value

main.py:

from dict_helper import DictHelper

# way1
d = DictHelper({})
d.set_key(['person', 'address', 'city'], 'New York')
print(d)

# or way2
d = DictHelper({})
d.set_key('person.address.city', 'New York')
print(d) 
# both way has the same result
# output={'person': {'address': {'city': 'New York'}}} 

您还可以在未来的项目中使用自己的辅助函数和辅助类。

2024 年 2 月 17 日新更新:

有一个非常好的图书馆可以让你更轻松地处理课程。只需通过以下命令安装它:

pip install "python-benedict[all or io for this project]"

现在您可以非常轻松地定义此类:

from collections import UserDict
from benedict import benedict


class DictHelper(UserDict):

def __init__(self, d: dict = None):
    super().__init__(d)
    self.d = benedict().from_json(self.data)

@staticmethod
def __keypath__(path: str | list):
    return path if isinstance(path, str) else '.'.join(path)

def has_key(self, path: str | list):
    return self.__keypath__(path) in self.d

def get_key(self, path: str | list):
    return self.d[self.__keypath__(path)]

def set_key(self, path: str | list, value=None):
    self.d[self.__keypath__(path)] = value

-5
投票

这在 Perl 中要容易得多:

my %hash;
$hash{"aaa"}{"bbb"}{"ccc"}=1;  # auto creates each of the intermediate levels
                               # of the hash (aka: dict or associated array)
© www.soinside.com 2019 - 2024. All rights reserved.