2

I have a dict with one of the keys have a value of list

example = {"a":1,"b":[1,2]}

I am trying to unpack example["b"] and create a list of the same dict with separate example["b"] value.

output = [{"a":1,"b":1},{"a":1,"b":2}]

I have tried to use a for loop to understand the unpacking and reconstruction of the list of dict but I am seeing a strange behavior - It could be I am missing something simple here...

iter = example.get("b")

new_list = []

for p in iter:
    print(f"p is {p}")
    tmp_dict = example
    tmp_dict["b"] = p
    print(tmp_dict)
    new_list.append(tmp_dict)

print(new_list)

Output

p is 1
{'a': 1, 'b': 1}
p is 2
{'a': 1, 'b': 2}
[{'a': 1, 'b': 2}, {'a': 1, 'b': 2}]

Why is the first dict in the list gets assigned with example["b"] = 2 although the first print() shows that p is 1?

Akmal
  • 474
  • 1
  • 4
  • 12
  • Because `tmp_dict` is the same dictionary(`example`) within the loop. There is only one dictionary instance, so last value assignment only has effect. You can use shallow copy to make different dictionary. `tmp_dict = example.copy()` – Boseong Choi Oct 18 '22 at 05:49
  • Plus, you shouldn't use `iter` as a variable name since it is a builtin name. You can avoid builtin name shadowing with trailing underscore. `iter_ = example.get("b")`. – Boseong Choi Oct 18 '22 at 05:51
  • 1
    Or you can shorten it to `new_list = [example | {'b': p} for p in example['b']]` (for python 3.9+); or `new_list = [{**example, 'b': p} for p in example['b']]` (3.5+). – j1-lee Oct 18 '22 at 05:52
  • Appending @j1-lee's comment, `new_list = [{**example, 'b': p} for p in example['b']]` would work in 3.8- versions. – Boseong Choi Oct 18 '22 at 05:53

2 Answers2

3

Why is the first dict in the list gets assigned with example["b"] = 2 although the first print() shows that p is 1

Because they are all the same dict: example. You can see this if you print(id(tmp_dict)) inside the loop, and you'll see that it is always the same object. See What is the id( ) function used for? (it's a python 2 question, but applies to python 3 as well)

If you want to keep your code, you just need to create a new dictionary from example and assign it to tmp_dict. See How to copy a dictionary and only edit the copy

tmp_dict = example.copy()

although that basically copies the entire dictionary only to discard the b key, so you could simply do:

tmp_dict = {"a": example["a"]}

Note, your use of iter as a variable name is not recommended, since iter already means something in python


Instead, here's a general approach that works for all cases without hardcoding any keys. Let's first create a temporary dictionary where all values are lists.

temp = {k: v if isinstance(v, list) else [v] for k, v in example.items()}

This allows us to then obtain the list of all the values in our temp dict as a list of lists.

We want the product of all the values of this temp dictionary. To do this, we can use the itertools.product function, and unpack our list of lists to its arguments.

In each iteration, the resulting tuple will have one value per key of the temp dictionary, so all we need to do is zip that with our tuple, and create a dict out of those key-value pairs. That gives us our list element!

import itertools

keys = list(temp.keys())
vals = list(temp.values())

result = []

for vals_product in itertools.product(*vals):
    d = dict(zip(keys, vals_product))
    result.append(d)

Which gives the required result:

[{'a': 1, 'b': 1}, {'a': 1, 'b': 2}]

This even works for an example with more keys:

example = {'a': 1, 'b': [1, 2], 'c': [1, 2, 3]}

which gives:

[{'a': 1, 'b': 1, 'c': 1},
 {'a': 1, 'b': 1, 'c': 2},
 {'a': 1, 'b': 1, 'c': 3},
 {'a': 1, 'b': 2, 'c': 1},
 {'a': 1, 'b': 2, 'c': 2},
 {'a': 1, 'b': 2, 'c': 3}]
Pranav Hosangadi
  • 23,755
  • 7
  • 44
  • 70
0

Just one minor correction: Usage of dict()

example = {"a":1,"b":[1,2]}

iter = example.get("b")

new_list = []

for p in iter:
    print(f"p is {p}")
    tmp_dict = dict(example)
    tmp_dict["b"] = p
    print(tmp_dict)
    new_list.append(tmp_dict)

print(new_list)

Output is as given below: [{'a': 1, 'b': 1}, {'a': 1, 'b': 2}]

Ashish Jain
  • 447
  • 1
  • 6
  • 20