Cannot rename Python dict key values with pop - Bug? - python

I cannot rename the Python dict using pop as described below. I've used suggestions from this post. Is this a Python bug? I'm using Python 3.5.2
>>> d = {'a': 'ABC', 'b': 'CDE', 'c': 'KRT'}
>>> for old_key in d.keys():
d['NEW_KEY_' + old_key] = d.pop(old_key)
>>> d
{'NEW_KEY_NEW_KEY_NEW_KEY_b': 'CDE', 'NEW_KEY_NEW_KEY_a': 'ABC', 'c': 'KRT'}

In Python 3, you have to change for old_key in d.keys() into for old_key in list(d.keys()). That should work because you are iterating over a dynamic entity.

It's mentioned in the documentation that changing a dictionary while iterating over it might fail to produce correct results. You'd better write something like
new_dict = {'NEW_KEY_' + key: value for key, value in old_dict.items()}

You are changing your keys as you iterate over a view of them. That's not recommended.
Instead, you can take a copy of your keys and iterate over them. Since dictionary keys are unique, you can use list or set:
d = {'a': 'ABC', 'b': 'CDE', 'c': 'KRT'}
for old_key in list(d):
d['NEW_KEY_' + old_key] = d.pop(old_key)
# {'NEW_KEY_a': 'ABC', 'NEW_KEY_b': 'CDE', 'NEW_KEY_c': 'KRT'}
You can, of course, use a dictionary comprehension, which you should find more efficient.

Related

Convert dictionary value from string into list with single element of type float

I have a dictionary that looks like this: d = {'a': '[1]', 'b': '[2]'}. I want to covert the string value into a float and have d = {'a': [1], 'b': [2]}, so the value should be a list with a single element of float type. How to do that?
{k:[float(v.replace(']','').replace('[',''))] for k, v in d.items()}
OR
{k:[float(v[1,-1])] for k, v in d.items()}
ast.literal_eval would create a list of int but OP required explicitly a list of float. One could:
import ast
{k: [float(a) for a in ast.literal_eval(v)] for k, v in d.items()}
You are probably looking for python's eval() function:
d = {'a': '[1]', 'b': '[2]'}
for k in d:
d[k] = eval(d[k])
print(d)
And the output:
{'a': [1], 'b': [2]}
EDIT
As stated in the comments, eval can be sometimes dangerous. So here's an alternative:
d = {'a': '[1]', 'b': '[2]'}
for k in d:
d[k] = [float(d[k][1:-1])] # because the list conatins a single element
print(d)
EDIT2
If you want the alternative to work with lists with multiple values:
replace this line
d[k] = [float(d[k][1:-1])]
with
d[k] = list(map(lambda x: float(x.strip()), d[k][1:-1].split(',')))
I will just add a reference to the Python PEP 274 syntactic sugar for this sort of thing, the Dict Comprehension:
>>> d = {'a': '[1]', 'b': '[2]'}
>>> d = {k: eval(v) for k, v in d.items()}
>>> d
{'a': [1], 'b': [2]}
This is slightly more compact, but works the exact same.
You also made the following note:
so the value should be a list with a single element of float type.
The above code could be modified to do this easily enough, but I should mention that, for the most part, you can use Python integers anywhere you would want a float.
>>> d = {'a': '[1]', 'b': '[2]'}
>>> d = {k: [float(eval(v)[0])] for k, v in d.items()}
>>> d
{'a': [1.0], 'b': [2.0]}
EDIT
I can't reply to your comment because of reputation, JoKing, but it's a great reminder. However, the typical use case for this conversion is probably not fetching source code from potentially malicious 3rd parties and executing it locally. So I would say that, in the case of simple data handling where you control the data io and your source code isn't public or published, eval() is absolutely fine.
OP added an additional question in regards to those mentioned dangers of eval():
So instead of '[1]' I have say '[1, 2]'
If you use previous methods to strip off the brackets, you can use .split(",") to separate the list into individuals strings to be converted. Using the dict comprehension is becoming more ugly here (because it contains a list comprehension to account for this new case), but it still works:
>>> d = {'a': '[1, 2]', 'b': '[2]'}
>>> d = {k: [float(i) for i in v[1:-1].split(",")] for k, v in d.items()}
{'a': [1.0, 2.0], 'b': [2.0]}

RuntimeError: dictionary changed size during iteration using Python3? [duplicate]

I have a dictionary of lists in which some of the values are empty:
d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}
At the end of creating these lists, I want to remove these empty lists before returning my dictionary. I tried doing it like this:
for i in d:
if not d[i]:
d.pop(i)
but I got a RuntimeError. I am aware that you cannot add/remove elements in a dictionary while iterating through it...what would be a way around this then?
See Modifying a Python dict while iterating over it for citations that this can cause problems, and why.
In Python 3.x and 2.x you can use use list to force a copy of the keys to be made:
for i in list(d):
In Python 2.x calling keys made a copy of the keys that you could iterate over while modifying the dict:
for i in d.keys():
But note that in Python 3.x this second method doesn't help with your error because keys returns an a view object instead of copying the keys into a list.
You only need to use copy:
This way you iterate over the original dictionary fields and on the fly can change the desired dict d.
It works on each Python version, so it's more clear.
In [1]: d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}
In [2]: for i in d.copy():
...: if not d[i]:
...: d.pop(i)
...:
In [3]: d
Out[3]: {'a': [1], 'b': [1, 2]}
(BTW - Generally to iterate over copy of your data structure, instead of using .copy for dictionaries or slicing [:] for lists, you can use import copy -> copy.copy (for shallow copy which is equivalent to copy that is supported by dictionaries or slicing [:] that is supported by lists) or copy.deepcopy on your data structure.)
Just use dictionary comprehension to copy the relevant items into a new dict:
>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = {k: v for k, v in d.items() if v}
>>> d
{'a': [1], 'b': [1, 2]}
For this in Python 2:
>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = {k: v for k, v in d.iteritems() if v}
>>> d
{'a': [1], 'b': [1, 2]}
This worked for me:
d = {1: 'a', 2: '', 3: 'b', 4: '', 5: '', 6: 'c'}
for key, value in list(d.items()):
if value == '':
del d[key]
print(d)
# {1: 'a', 3: 'b', 6: 'c'}
Casting the dictionary items to list creates a list of its items, so you can iterate over it and avoid the RuntimeError.
I would try to avoid inserting empty lists in the first place, but, would generally use:
d = {k: v for k,v in d.iteritems() if v} # re-bind to non-empty
If prior to 2.7:
d = dict( (k, v) for k,v in d.iteritems() if v )
or just:
empty_key_vals = list(k for k in k,v in d.iteritems() if v)
for k in empty_key_vals:
del[k]
To avoid "dictionary changed size during iteration error".
For example: "when you try to delete some key",
Just use 'list' with '.items()'. Here is a simple example:
my_dict = {
'k1':1,
'k2':2,
'k3':3,
'k4':4
}
print(my_dict)
for key, val in list(my_dict.items()):
if val == 2 or val == 4:
my_dict.pop(key)
print(my_dict)
Output:
{'k1': 1, 'k2': 2, 'k3': 3, 'k4': 4}
{'k1': 1, 'k3': 3}
This is just an example. Change it based on your case/requirements.
For Python 3:
{k:v for k,v in d.items() if v}
You cannot iterate through a dictionary while itโ€™s changing during a for loop. Make a casting to list and iterate over that list. It works for me.
for key in list(d):
if not d[key]:
d.pop(key)
Python 3 does not allow deletion while iterating (using the for loop above) a dictionary. There are various alternatives to do it; one simple way is to change the line
for i in x.keys():
with
for i in list(x)
The reason for the runtime error is that you cannot iterate through a data structure while its structure is changing during iteration.
One way to achieve what you are looking for is to use a list to append the keys you want to remove and then use the pop function on dictionary to remove the identified key while iterating through the list.
d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}
pop_list = []
for i in d:
if not d[i]:
pop_list.append(i)
for x in pop_list:
d.pop(x)
print (d)
For situations like this, I like to make a deep copy and loop through that copy while modifying the original dict.
If the lookup field is within a list, you can enumerate in the for loop of the list and then specify the position as the index to access the field in the original dict.
Nested null values
Let's say we have a dictionary with nested keys, some of which are null values:
dicti = {
"k0_l0":{
"k0_l1": {
"k0_l2": {
"k0_0":None,
"k1_1":1,
"k2_2":2.2
}
},
"k1_l1":None,
"k2_l1":"not none",
"k3_l1":[]
},
"k1_l0":"l0"
}
Then we can remove the null values using this function:
def pop_nested_nulls(dicti):
for k in list(dicti):
if isinstance(dicti[k], dict):
dicti[k] = pop_nested_nulls(dicti[k])
elif not dicti[k]:
dicti.pop(k)
return dicti
Output for pop_nested_nulls(dicti)
{'k0_l0': {'k0_l1': {'k0_l2': {'k1_1': 1,
'k2_2': 2.2}},
'k2_l1': 'not '
'none'},
'k1_l0': 'l0'}
The Python "RuntimeError: dictionary changed size during iteration" occurs when we change the size of a dictionary when iterating over it.
To solve the error, use the copy() method to create a shallow copy of the dictionary that you can iterate over, e.g., my_dict.copy().
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key in my_dict.copy():
print(key)
if key == 'b':
del my_dict[key]
print(my_dict) # ๐Ÿ‘‰๏ธ {'a': 1, 'c': 3}
You can also convert the keys of the dictionary to a list and iterate over the list of keys.
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key in list(my_dict.keys()):
print(key)
if key == 'b':
del my_dict[key]
print(my_dict) # ๐Ÿ‘‰๏ธ {'a': 1, 'c': 3}
If the values in the dictionary were unique too, then I used this solution:
keyToBeDeleted = None
for k, v in mydict.items():
if(v == match):
keyToBeDeleted = k
break
mydict.pop(keyToBeDeleted, None)

Double filter for a list of dictionaries in Python

I've read a number of articles and forum posts but haven't quite figured out how to improve on the below. I have a list of dictionaries that need to be filtered in two ways.
jsonData = [{'a':'1a', 'b':'1b', 'c':'1c'},{'a':'2a','b':'2b', 'c':'2c'}, {'a':'3a','b':'3b', 'c':'3c'}]
I want to remove any dictionary within the list where the key c corresponds to 3c. The first line below achieves that. Then I want to only retain the b and c keys and corresponding values. This is what the rest does. It all works but I'm wondering if the second part can also be expressed in a list comprehension and if there are any other ways to simplify this.
jsonData = [i for i in jsonData if i['c'] != '3c']
for i in range(len(jsonData)):
jsonData[i] = {key:value for key,value in jsonData[i].items() if key in ['b','c']}
Thank you very much
You can use the set-like nature of keyviews to limit the search of d to only the keys you'd actually use instead of iterating all of them.
jsonData = [{k: d[k] for k in (d.keys() & {'b', 'c'})} for d in jsonData if d['c'] != '3c']
If this is on Python 2.7 instead of Python 3.x, then d.keys() should be d.viewkeys().
I believe something like this would do the trick:
[{key:value for key,value in jdata.items() if key in ['b', 'c']}
for jdata in jsonData if jdata['c'] != '3c']
it returns
[{'c': '1c', 'b': '1b'}, {'c': '2c', 'b': '2b'}]
given the provided list.

Pick a key from a dictionary python

go through a dictionary picking keys from it in a loop?
For example lets say I have the following dictionary: {'hello':'world', 'hi':'there'}. Is there a way to for loop through the dictionary and print hello, hi?
on a similar note is there a way to say myDictionary.key[1] and that will return hi?
You can iterate over the keys of a dict with a for loop:
>>> for key in yourdict:
>>> print(key)
hi
hello
If you want them as a comma separated string you can use ', '.join.
>>> print(', '.join(yourdict))
hi, hello
on a similar note is there a way to say myDictionary.key1 and that will return hi
No. The keys in a dictionary are not in any particular order. The order that you see when you iterate over them may not be the same as the order you inserted them into the dictionary, and also the order could in theory change when you add or remove items.
if you need an ordered collection you might want to consider using another type such as a list, or an OrderedDict
You can use the .keys() method:
for key in myDictionary.keys():
print(key)
You can also use .items() to iterate through both at the same time:
for key, value in myDictionary.items():
print(key, value)
Using the dictionary name as a sequence produces all the keys:
>>> d={'hello':'world', 'hi':'there'}
>>> list(d)
['hi', 'hello']
so list({'hello':'world', 'hi':'there'})[1] produces element 1 of the list of keys.
This is of limited use, however, because dictionaries are unordered. And their order may be different than the order of insertion:
>>> d={'a': 'ahh', 'b': 'baa', 'c': 'coconut'}
>>> d
{'a': 'ahh', 'c': 'coconut', 'b': 'baa'}
You can do sorted(list({'hello':'world', 'hi':'there'}))[1] for the 1 element of a sorted list of the keys of your dict. That produces 'hi' in this case. Not the most readable or efficient though...
You should look at OrderedDict if you want a sorted order.
Or just sort into a list:
>>> d={'a': 'ahh', 'b': 'baa', 'c': 'coconut'}
>>> l=[(k,v) for k, v in d.items()]
>>> l.sort()
>>> l[1]
('b', 'baa')
>>> l[1][0]
'b'
You can reverse (k,v) to (v,k) if you want to sort by value instead of by key.
dict.iterkeys in Python 2, dict.keys in Python 3.
d = { 'hello': 'world', 'hi': 'there' }
for key in d.iterkeys():
print key
Sounds like a list of keys would meet your needs:
>>> d = { 'hello': 'world', 'hi': 'there' }
>>> keys = list(d)
>>> keys
['hi', 'hello']
>>> from random import choice
>>> choice(keys)
'hi'

Get all values from child elements of a dict as a list

T = {'a': {'c': 'A', 'path': '/c'}, 'e': {'c': 'E', 'path': '/e'}, 's': {'c': 'S', 'path': '/s'}}
I need all 'path' elements as a list. I know I can iterate through everything in a single for, but I would like to know other pythonic ways of extracting it.
Think that there is no way except iterate so you can use the following code:
[v['path'] for k,v in T.iteritems() if k in <top level keys to be used>]
EDITED as per comments: I have used iteritems since it allows you to perform filter action using top level keys if neccessary, otherwise(if you don't care about top level keys) it is better to use #phant0m's solution with itervalues.
[v['path'] for v in T.itervalues() if k in <top level keys to be used>]
OR dict.get:
[v.get('path') for v in T.itervalues()]
OR, just for completeness, not so fast but using map and lambda:
from operator import itemgetter
map(itemgetter('path'), T.itervalues())
Or simpler:
>>> [x["path"] for x in T.itervalues()]
['/c', '/s', '/e']

Categories

Resources