Python: nested dictionary is changing all values at once - python

I need to generate nested dictionaries, so I wrote the function create_nested:
def create_nested():
key_dict = {}
for key in ['key1', 'key2']:
key_dict[key] = 0
nested_dict = {}
for dictionary in ['dict1', 'dict2']:
nested_dict[dictionary] = key_dict
return nested_dict
Which will always return something like this:
{'dict1': {'key1': 0, 'key2': 0}, 'dict2': {'key1': 0, 'key2': 0}}
When a try to change one of the values this way:
x=create_nested()
x['dict1']['key2'] = 2
It gives me {'dict1': {'key1': 0, 'key2': 2}, 'dict2': {'key1': 0, 'key2': 2}}
Instead of {'dict1': {'key1': 0, 'key2': 2}, 'dict2': {'key1': 0, 'key2': 0}}
What am I doing wrong?

Your dict1 and dict2 keys are referencing to the same dict object created before so any change performed on it will impact both key's values.
If you want to assign to your keys two different dicts, you should copy it:
import copy
def create_nested():
key_dict = {}
for key in ['key1', 'key2']:
key_dict[key] = 0
nested_dict = {}
for dictionary in ['dict1', 'dict2']:
nested_dict[dictionary] = copy.deepcopy(key_dict)
deepcopy will copy recursively all the elements inside your dict and will create an independent copy for you, so any change performed on it will impact only itself.

When you assign the key_dict to multiple values in nested_dict, they will refer to the same object in memory (I hope my terminology is correct. Python is Pass-by-object-reference, full explaination here https://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick/ )
for dictionary in ['dict1', 'dict2']:
nested_dict[dictionary] = key_dict
return nested_dict
now if I change nested_dict['dict1'] I'm changing the same memory that nested_dict['dict2'] knows about. In general, know which types are mutable, and expect them to change if another reference to it changes it.
A simple fix for dictionaries that only have immutable values is:
for dictionary_name in ['dict1', 'dict2']: # The variable name dictionary for a string is very confusing, so changing that.
# create a new dictionary in memory
nested_dict[dictionary_name] = {k: v for k, v in key_dict.items()}
return nested_dict
If you have potentially values in key_dict that are mutable then you must make a deep copy. https://docs.python.org/3/library/copy.html

Related

Create new nested dictionary using nested key, values

I have a dictionary of key-value pairs, where the value is itself a dictionary. I would like to change the names of key values in that nested dictionary based on a predefined conversion.
I am using two lists to match up the values of the nested keys that I am trying to conver (Old_item1 should become New_item1):
comparison_list = ['Old_item1', 'Old_item2']
new_prods = ['New_item1', 'New_item2']
old_dict = {
'Company1':
{
'Old_item1':
{
'key1': val,
'key2': val
},
'Old_item2':
{
'key1': val,
'key2': val
}
}
}
I tried this:
new_dict = {}
for i in comparison_list:
for j in new_prods:
new_dict['Company1'][j] = test['Company1'][i]
I get a KeyError: KeyError: 'Company1'
The desired output for each item I add to each list is:
new_dict = {
'Company1':
{
'New_item1':
{
'key1': val # old item key, val
'key2': val # old item key, val
}
}
}
You can make a dictionary of mappings from the old to the new items, and then use it to create a new sub-dictionary for each company. This then needs to be wrapped inside an outer loop over companies (although here there is only one).
For example:
comparison_list = ['Old_item1', 'Old_item2']
new_prods = ['New_item1', 'New_item2']
old_dict = {'Company1':
{'Old_item1':
{'key1': 2,
'key2': 3},
'Old_item2':
{'key1': 4,
'key2': 5}}}
key_mappings = dict(zip(comparison_list, new_prods))
new_dict = {k: {key_mappings[k1]: v1 for k1, v1 in v.items()}
for k, v in old_dict.items()}
print(new_dict)
gives:
{'Company1': {'New_item1': {'key1': 2, 'key2': 3}, 'New_item2': {'key1': 4, 'key2': 5}}}
Here is the mappings dictionary key_mappings which we used:
{'Old_item1': 'New_item1', 'Old_item2': 'New_item2'}
The easiest way to go about this is to re-create your dictionary with a comprehension:
new_dict = {company: update_item(v) for company, v in old_dict.items()}
Then parse out update_item to it's own function. You could do this inline but it makes it difficult to understand.
conversion_lookup = {
'Old_item1': 'NewItem1',
'Old_item2': 'NewItem2',
}
def update_item(item: dict) -> dict:
return { conversion_lookup.get(k, k): v for k, v in item.items() }
new_dict = {company: update_item(v) for company, v in old_dict.items()}
For the conversion here I'm using a dictionary describing the conversion. If you need to construct this automatedly:
comparison_list = ['Old_item1', 'Old_item2']
new_prods = ['New_item1', 'New_item2']
conversion_lookup = { v: new_prods[idx] for idx, v in enumerate(comparison_list) }
The reason I like a dictionary is that you can use some_dict.get(a_value, a_default_value). Then, if your value isn't in your conversion dictionary you can fall back to the original value. That's what I'm doing here:
conversion_lookup.get(k, k)
The second k is the original item's value, which is a good thing to use if your conversion list doesn't include what you want.
One approach is to do it step by step. First create the dicts with the new names and old keys, then remove the old keys, and finally add in the new names.
# Keep the values from the old keys
values = [old_dict['Company1'][old_name] for old_name in comparison_list]
# Remove old names, and add in the
for new_key, value, old in zip(new_prods, values, comparison_list):
old_dict['Company1'].pop(old)
old_dict['Company1'][new_key] = value

Defaultdict that automatically creates a sub-dict for each key? [duplicate]

This question already has answers here:
What is the best way to implement nested dictionaries?
(22 answers)
Closed 1 year ago.
I have a dictionary where I am constantly doing stuff like this in my code:
special_dict = {}
# ...
if username not in special_dict:
special_dict[username] = {}
for subkey in ["Subkey1", "Subkey2", "Subkey3"]:
special_dict[username][subkey] = [] # or {}, etc, depending on usecase
Basically I want a dictionary where for every username, the value is yet another dictionary of three specific subkeys, and then those values are lists or sets or what have you.
I'm familiar with defaultdict but I am not sure how to make the "value type" here something very specific. Normally I do defaultdict(list) if I want every value to be a list by default, but is there a way to make the default not a list but in itself a specific type of dictionary?
Ideally, in the end what I want to be able to do is special_dict[username][subkey].append(item) and not have to worry about whether or not the username exists first, because if it doesn't, it'll become a key and have the three subkeys formed.
You need a function that will create the structure you want, and pass this function as argument to defaultdict:
from collections import defaultdict
def name_subdict():
return {'key1':[], 'key2':set(), 'key3':{}}
mydict = defaultdict(name_subdict)
mydict['John']['key1'].append(1)
mydict['John']['key2'].add(2)
mydict['Jane']['key3'][10] = 20
print(mydict)
# defaultdict(<function name_subdict at 0x7fcaf81193a0>,
# {'John': {'key1': [1], 'key2': {2}, 'key3': {}},
# 'Jane': {'key1': [], 'key2': set(), 'key3': {10: 20}}})
To answer your comment: yes, you can pass the type of data you want to be used for all subkeys, as in mydict = name_subdict(list). There is only one caveat: the argument to defaultdict must be a function (or any callable) that takes no argument.
So, name_subdict(list) should return a function that will in turn create the structure.
The code would then be:
from collections import defaultdict
def name_subdict(data_type):
# data type must be a callable like list, set, dict...
def subdict_creator():
return {key:data_type() for key in ['key1', 'key2', 'key3']}
return subdict_creator
my_list_dict = defaultdict(name_subdict(list))
my_set_dict = defaultdict(name_subdict(set))
my_list_dict['John']['key1'].append(1)
my_list_dict['John']['key2'].append(2)
my_set_dict['Jane']['key3'].add(10)
print(my_list_dict)
# defaultdict(<function name_subdict.<locals>.subdict_creator at 0x7fcadbf27b80>,
# {'John': {'key1': [1], 'key2': [2], 'key3': []}})
print(my_set_dict)
# defaultdict(<function name_subdict.<locals>.subdict_creator at 0x7fcadbbf25e0>,
# {'Jane': {'key1': set(), 'key2': set(), 'key3': {10}}})

python dictionary key pointing to value of another key in the same dictionary

I have a python dictionary that has the below format
data = { 'key1': 'value1',
'key2': 'value2',
'key3': 'value3'
}
I want this key3 value to point to the value of key1.
what i tried so far is
'key3': key1['value1'] or 'key3': data['key1'] and both of them seem to be invalid syntaxes.
Python doesn't support pointers like C/C++ does, but you could use lists to serve as references. To access the value, you'd index into the first element of the list.
data = {
'key1': ['value1']
}
data['key3'] = data['key1'] # copy the list by reference
print(f"Old: {data['key3'][0]}, {data['key3'][0] == data['key1'][0]}")
data['key1'][0] = 'new-value' # this will modify the value from data['key3'] as well
print(f"New: {data['key3'][0]}, {data['key3'][0] == data['key1'][0]}")
Output:
Old: value1, True
New: new-value, True
Note that, this assumes that you're fully aware of which values act as "pointers" and which ones don't.
For example,
data = {
'key1': ['pointer-list'], # should act as a "pointer"
'key2': ['normal', 'list'] # should act as a normal list
}
data['key3'] = data['key1'] # copy list by reference
data['key1'][0] = 'new-value' # propogate new value to other references
data['key4'] = data['key2'] # oops – copy list by reference
data['key2'][0] = 'new-value' # oops – data['key2'] should act as a normal list but
# the new value is propogated to data['key4'] as well
To deal with this issue, clone or copy the list instead.
import copy
data['key4'] = copy.copy(data['key2'])
# data['key4'] = copy.deepcopy(data['key2']) # if the value contains nested lists
# data['key4'] = data['key2'].copy() # another way
Try this
data = { 'key1': 'value1', 'key2': 'value2', 'key3': 'value3' }
data['key3'] = data['key1']
print(data)
Prints out:
{'key3': 'value1', 'key2': 'value2', 'key1': 'value1'}

Python3.6 4-level dictionaries giving KeyError [duplicate]

This question already has answers here:
Nested defaultdict of defaultdict
(11 answers)
Closed 5 years ago.
I want to deal with a nested dictionary in python for purpose of storing unique data. However, I don't know what the right way to do it. I tried the following:
my_dict = collections.defaultdict(dict)
my_dict[id1][id2][id2][id4] = value
but it causes KeyError.
What is the right way to do so?
If you want to create a nested defaultdict to as many depths as you want then you want to set the default type of the defaultdict to a function that returns a defaultdict with the same type. So it looks a bit recursive.
from collections import defaultdict
def nest_defaultdict():
return defaultdict(nest_defaultdict)
d = defaultdict(nest_defaultdict)
d[1][2][3] = 'some value'
print(d)
print(d[1][2][3])
# Or with lambda
f = lambda: defaultdict(f)
d = defaultdict(f)
If you don't require any arbitrary depth then Fuji Clado's answer demonstrates setting up the nested dict and accessing it.
One Simple Approach
mainDict = {}
mainDict['id1']={}
mainDict['id1']['id2'] ={}
mainDict['id1']['id2']['id3'] = 'actualVal'
print(mainDict)
# short explanation of defaultdict
import collections
# when a add some key to the mainDict, mainDict will assgin
# an empty dictionary as the value
mainDict = collections.defaultdict(dict)
# adding only key, The value will be auto assign.
mainDict['key1']
print(mainDict)
# defaultdict(<class 'dict'>, {'key1': {}})
# here adding the key 'key2' but we are assining value of 2
mainDict['key2'] = 2
print(mainDict)
#defaultdict(<class 'dict'>, {'key1': {}, 'key2': 2})
# here we are adding a key 'key3' into the mainDict
# mainDict will assign an empty dict as the value.
# we are adding the key 'inner_key' into that empty dictionary
# and the value as 10
mainDict['key3']['inner_key'] = 10
print(mainDict)
#defaultdict(<class 'dict'>, {'key1': {}, 'key2': 2, 'key3': {'inner_key': 10}})

Python remove keys with the same value on a dictionary

I need to do a not "natural" operation on a dictionary so i wondering what is the best pythonic way to do this.
I need to simplify a dictionary by removing all the keys on it with the same value (keys are differents, values are the same)
For example:
Input:
dict = {key1 : [1,2,3], key2: [1,2,6], key3: [1,2,3]}
expected output:
{key1 : [1,2,3], key2:[1,2,6]}
I dont care about which key is delete (on the example: key1 or key3)
Exchange keys and values; duplicated key-value pairs will be removed as a side effect (because dictionary does not allow duplicated keys). Exchange keys and values again.
>>> d = {'key1': [1,2,3], 'key2': [1,2,6], 'key3': [1,2,3]}
>>> d2 = {tuple(v): k for k, v in d.items()} # exchange keys, values
>>> d = {v: list(k) for k, v in d2.items()} # exchange again
>>> d
{'key2': [1, 2, 6], 'key1': [1, 2, 3]}
NOTE: tuple(v) was used because list is not hashable; cannot be used as key directly.
BTW, don't use dict as a variable name. It will shadow builtin function/type dict.
This solution deletes the keys with same values without creating a new dictionary.
seen = set()
for key in mydict.keys():
value = tuple(mydict[key])
if value in seen:
del mydict[key]
else:
seen.add(value)
I think you can do it this way also. But I don't say as there seems to be more efficient ways. It is in-line.
for i in dictionary.keys():
if dictionary.values().count(dictionary[i]) > 1:
del dictionary[i]
You can iterate over your dict items and use a set to check what we have seen so far, deleting a key if we have already seen the value:
d = {"key1" : [1,2,3], "key2": [1,2,6], "key3": [1,2,3]}
seen = set()
for k, v in d.items(): # list(items) for python3
temp = tuple(v)
if temp in seen:
del d[k]
seen.add(temp)
print(d)
{'key1': [1, 2, 3], 'key2': [1, 2, 6]}
This will be more efficient that using creating a dict and reversing the values as you only have to cast to tuple once not from a tuple back to a list.
this worked for me:
seen = set()
for key in mydict.copy():
value = tuple(mydict[key])
if value in seen:
del mydict[key]
else:
seen.add(value)

Categories