How to insert key-value pair into dictionary at a specified position?
Asked Answered
C

9

35

How would I insert a key-value pair at a specified location in a python dictionary that was loaded from a YAML document?

For example if a dictionary is:

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

I wish to insert the element 'Phone':'1234' before 'Age', and after 'Name' for example. The actual dictionary I shall be working on is quite large (parsed YAML file), so deleting and reinserting might be a bit cumbersome (I don't really know).

If I am given a way of inserting into a specified position in an OrderedDict, that would be okay, too.

Chronicles answered 6/6, 2017 at 13:5 Comment(1)
Had the same issue with a parsed YAML file inPython 3.6.9 and solved it as described here https://mcmap.net/q/432488/-how-to-insert-key-value-pair-into-dictionary-at-a-specified-positionCarnay
C
17

Had the same issue and solved this as described below without any additional imports being required and only a few lines of code. Tested with Python 3.6.9.

  1. Get position of key 'Age' because the new key value pair should get inserted before
  2. Get dictionary as list of key value pairs
  3. Insert new key value pair at specific position
  4. Create dictionary from list of key value pairs
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'}

Edit 2021-12-20:
Just saw that there is an insert method available ruamel.yaml, see the example from the project page:

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)

Carnay answered 26/11, 2021 at 12:32 Comment(1)
For additional context, and due to ruamel.yaml.YAML.load() returning any making it impossible for VSCode to find the method signature for CommentedMap.insert() without a type declaration, here's a link to the source code. github.com/commx/ruamel-yaml/blob/…. If you would like to add type declarations, you can import it via from ruamel.yaml.comments import CommentedMap.Declaratory
E
12

On python < 3.7 (or cpython < 3.6), you cannot control the ordering of pairs in a standard dictionary.

If you plan on performing arbitrary insertions often, my suggestion would be to use a list to store keys, and a dict to store values.

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

If you plan on initialising a dictionary with ordering whose contents are not likely to change, you can use the collections.OrderedDict structure which maintains insertion order.

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

Note that OrderedDict does not support insertion at arbitrary positions (it only remembers the order in which keys are inserted into the dictionary).

Enthusiast answered 6/6, 2017 at 13:9 Comment(3)
The problem is yaml.load loads a yaml file as a dictionary of dictionaries... (and so on). So editing has to be done on dictionaries.Chronicles
@DeepApp. The solution is simple. Convert the dict to a list. Example: dictAsList = [(k, v) for k, v in yourDict.items()]. Now you just need to iterate over your dictAsList and insert your items where you like.Enthusiast
If you start with YAML, you will not necessarily preserve the key order as is available from the YAML document if you do this. It is much preferable to use the RoundTripLoader if you want to preserve the order, it was made (among other things) for that purpose.Pennsylvanian
C
7

You will have to initialize your dict as OrderedDict. Create a new empty OrderedDict, go through all keys of the original dictionary and insert before/after when the key name matches.

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)
Cleanup answered 25/3, 2019 at 11:32 Comment(1)
Not on CPython 3.6 or any other Python 3.7. dicts keep insertion order now, there's no need to use OrderedDict for thisPyrrhuloxia
W
2

This is a follow-up on nurp's answer. Has worked for me, but offered with no warranty.

# 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')])
Waac answered 16/6, 2020 at 10:9 Comment(0)
P
1

Would this be "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:] }

INPUT: new_pair=('Phone',1234) , old_key='Age'

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

Partida answered 9/9, 2020 at 21:29 Comment(0)
C
1

This is a whole lot simpler than the other answers:

def insert_into_dict(existing, pos, key, value):
    keys = list(existing.keys())
    keys.insert(pos, key)
    return {k: existing.get(k, value) for k in keys}
Cavefish answered 8/2 at 13:28 Comment(0)
T
0

Simple reproducible example (using zip() for unpacking and packing)

### 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'}
Tutorial answered 20/8, 2021 at 14:9 Comment(0)
P
-2

Once your have used load() (without option Loader=RoundTripLoader) and your data is in a dict() it is to late, as the order that was available in the YAML file is normally gone (the order depending on the actual keys used, the python used (implementation, version and possible compile options).

What you need to do is use 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)

this will invariable give:

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

Under the hood round_trip_dump() transparently gives you back a subclass of orderddict to make this possible (which actual implementation is dependent on your Python version).

Pennsylvanian answered 6/6, 2017 at 14:5 Comment(0)
A
-3

Since your elements comes in pairs, I think this will could work.

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

    dict = {**dict,**new_element}

    print(dict)

This is the output I got:

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

Adjourn answered 9/4, 2020 at 17:23 Comment(1)
Does not address the question : "specified location"Sanctimony

© 2022 - 2024 — McMap. All rights reserved.