如何在字典的指定位置插入键值对?

29

如何在从YAML文档加载的Python字典中的指定位置插入键值对?

例如,如果字典是:

dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}

我希望在'Age'之前,'Name'之后插入元素'Phone':'1234'。我将要处理的实际字典非常大(解析的YAML文件),因此删除和重新插入可能有点麻烦(我不是很确定)。

如果我能够找到一种在OrderedDict中插入到指定位置的方法,那也可以。


在Python 3.6.9中,我遇到了解析YAML文件的相同问题,并按照此处描述的方式解决了它 https://dev59.com/VlcP5IYBdhLWcg3wcZm0#70124500 - ai2ys
8个回答

13

遇到了同样的问题,按照下面的方式解决,不需要任何额外的导入,只需要几行代码即可。已在Python 3.6.9中测试通过。

  1. 获取 'Age' 键的位置,因为新的键值对应该在其之前插入
  2. 将字典作为键值对列表获取
  3. 在特定位置插入新的键值对
  4. 从键值对列表创建字典
mydict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
print(mydict)
# {'Name': 'Zara', 'Age': 7, 'Class': 'First'}

pos = list(mydict.keys()).index('Age')
items = list(mydict.items())
items.insert(pos, ('Phone', '123-456-7890'))
mydict = dict(items)

print(mydict)
# {'Name': 'Zara', 'Phone': '123-456-7890', 'Age': 7, 'Class': 'First'}

编辑于2021年12月20日:
刚刚发现ruamel.yaml提供了一个insert方法,可以看一下该项目页面上的示例:

import sys
from ruamel.yaml import YAML

yaml_str = """\
first_name: Art
occupation: Architect  # This is an occupation comment
about: Art Vandelay is a fictional character that George invents...
"""

yaml = YAML()
data = yaml.load(yaml_str)
data.insert(1, 'last name', 'Vandelay', comment="new key")
yaml.dump(data, sys.stdout)


2
为了提供更多的背景信息,并且由于ruamel.yaml.YAML.load()返回的是any类型,这使得VSCode无法在没有类型声明的情况下找到CommentedMap.insert()方法的签名。因此,我在这里提供了源代码的链接:https://github.com/commx/ruamel-yaml/blob/a1b222e22a20365b2a338b0281f4b8c2b133da7e/comments.py#L882。如果您想要添加类型声明,可以通过`from ruamel.yaml.comments import CommentedMap`进行导入。 - Swirle13

12
在Python 3.7之前(或者CPython 3.6之前),您无法控制标准字典中键值对的顺序。
如果您计划经常进行任意插入操作,我建议使用列表存储键,使用字典存储值。
mykeys = ['Name', 'Age', 'Class']
mydict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'} # order doesn't matter

k, v = 'Phone', '123-456-7890'

mykeys.insert(mykeys.index('Name')+1, k)
mydict[k] = v

for k in mykeys:
    print(f'{k} => {mydict[k]}')

# Name => Zara
# Phone => 123-456-7890
# Age => 7
# Class => First

如果你计划使用顺序化的字典来存储不太可能改变的内容,可以使用 collections.OrderedDict 结构,它会维护插入的顺序。
from collections import OrderedDict

data = [('Name', 'Zara'), ('Phone', '1234'), ('Age', 7), ('Class', 'First')] 
odict = OrderedDict(data)
odict
# OrderedDict([('Name', 'Zara'),
#              ('Phone', '1234'),
#              ('Age', 7),
#              ('Class', 'First')])

请注意,OrderedDict不支持在任意位置插入元素(它只会记住将键插入字典的顺序)。

1
问题在于yaml.load将一个yaml文件加载为字典的字典...(等等)。因此,必须对字典进行编辑。 - DeepApp
2
@DeepApp。解决方案很简单。将字典转换为列表。例如:dictAsList = [(k, v) for k, v in yourDict.items()]。现在,您只需要遍历dictAsList并在需要的位置插入您的项目即可。 - cs95
2
如果你使用YAML开始编程,那么如果不使用RoundTripLoader,你将不能保留从YAML文档中获得的键顺序。如果你想要保留顺序,最好使用RoundTripLoader,它是专门为此目的而设计的(除其他之外)。 - Anthon

7

你需要将字典初始化为OrderedDict。创建一个新的空OrderedDict,遍历原始字典的所有键,当键名匹配时,在前面/后面插入。

from pprint import pprint
from collections import OrderedDict


def insert_key_value(a_dict, key, pos_key, value):
    new_dict = OrderedDict()
    for k, v in a_dict.items():
        if k==pos_key:
            new_dict[key] = value  # insert new key
        new_dict[k] = v
    return new_dict


mydict = OrderedDict([('Name', 'Zara'), ('Age', 7), ('Class', 'First')])
my_new_dict = insert_key_value(mydict, "Phone", "Age", "1234")
pprint(my_new_dict)

3
现在不仅限于CPython 3.6或任何其他Python 3.7版本,dict会保持插入顺序,因此不需要使用OrderedDict - user3064538

2

这是对nurp答案的跟进。它对我起作用了,但不带任何保修。

# Insert dictionary item into a dictionary at specified position: 
def insert_item(dic, item={}, pos=None):
    """
    Insert a key, value pair into an ordered dictionary.
    Insert before the specified position.
    """
    from collections import OrderedDict
    d = OrderedDict()
    # abort early if not a dictionary:
    if not item or not isinstance(item, dict):
        print('Aborting. Argument item must be a dictionary.')
        return dic
    # insert anywhere if argument pos not given: 
    if not pos:
        dic.update(item)
        return dic
    for item_k, item_v in item.items():
        for k, v in dic.items():
            # insert key at stated position:
            if k == pos:
                d[item_k] = item_v
            d[k] = v
    return d

d = {'A':'letter A', 'C': 'letter C'}
insert_item(['A', 'C'], item={'B'})
## Aborting. Argument item must be a dictionary.

insert_item(d, item={'B': 'letter B'})
## {'A': 'letter A', 'C': 'letter C', 'B': 'letter B'}

insert_item(d, pos='C', item={'B': 'letter B'})
# OrderedDict([('A', 'letter A'), ('B', 'letter B'), ('C', 'letter C')])

1
这是否符合"Pythonic"的风格?
def add_item(d, new_pair, old_key): #insert a newPair (key, value) after old_key
    n=list(d.keys()).index(old_key)
    return {key:d.get(key,new_pair[1]) for key in list(d.keys())[:n+1] +[new_pair[0]] + list(d.keys())[n+1:] }

输入:new_pair=('Phone',1234) , old_key='Age'

输出:{'Name': 'Zara', 'Age': 7, 'Phone': 1234, 'Class': 'First'}


0

简单可重现的示例(使用 zip() 进行解包和打包)

### Task - Insert 'Bangladesh':'Dhaka' after 'India' in the capitals dictinary

## Given dictionary
capitals = {'France':'Paris', 'United Kingdom':'London', 'India':'New Delhi',
            'United States':'Washington DC','Germany':'Berlin'}


## Step 1 - Separate into 2 lists containing : 1) keys, 2) values 
country, cap = (list(tup) for tup in zip(*capitals.items()))
# or
country, cap = list(map(list, zip(*capitals.items())))

print(country)
#> ['France', 'United Kingdom', 'India', 'United States', 'Germany']
print(cap)
#> ['Paris', 'London', 'New Delhi', 'Washington DC', 'Berlin']


## Step 2 - Find index of item before the insertion point (from either of the 2 lists)
req_pos = country.index('India')
print(req_pos)
#> 2


## Step 3 - Insert new entry at specified position in both lists
country.insert(req_pos+1, 'Bangladesh')
cap.insert(req_pos+1, 'Dhaka')

print(country)
#> ['France', 'United Kingdom', 'India', 'Bangladesh', 'United States', 'Germany']
print(cap)
#> ['Paris', 'London', 'New Delhi', 'Dhaka', 'Washington DC', 'Berlin']


## Step 4 - Zip up the 2 lists into a dictionary
capitals = dict(zip(country, cap))
print(capitals)
#> {'France': 'Paris', 'United Kingdom': 'London', 'India': 'New Delhi', 'Bangladesh': 'Dhaka', 'United States': 'Washington DC', 'Germany': 'Berlin'}

-1

一旦您使用了load()(没有选项Loader=RoundTripLoader),并且您的数据在dict()中,那么为时已晚,因为YAML文件中可用的顺序通常已经消失了(顺序取决于实际使用的键、使用的Python(实现、版本和可能的编译选项)。

您需要做的是使用round_trip_load()

import sys
from ruamel import yaml

yaml_str = "{'Name': 'Zara', 'Age': 7, 'Class': 'First'}"

data = yaml.round_trip_load(yaml_str)
pos = list(data.keys()).index('Age')  # determine position of 'Age'
# insert before position of 'Age'
data.insert(pos, 'Phone', '1234', comment='This is the phone number')
data.fa.set_block_style()   # I like block style
yaml.round_trip_dump(data, sys.stdout)

这将不可避免地给出:

Name: Zara
Phone: '1234'  # This is the phone number
Age: 7
Class: First

在幕后,round_trip_dump() 会透明地为您提供 orderddict 的子类,以使这一切成为可能(其实际实现取决于您的 Python 版本)。


-2

由于您的元素是成对出现的,我认为这个方法可能有效。

    dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
    new_element = { 'Phone':'1234'}

    dict = {**dict,**new_element}

    print(dict)


这是我得到的输出:
   {'Name': 'Zara', 'Age': 7, 'Class': 'First', 'Phone': '1234'}


3
未解答问题:“指定位置”。 - andreis11

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接