Search code examples
pythonpython-3.xpython-itertoolsdefault-valuemutable

Python itertools.zip_longest with mutable fillvalue


In a code that evaluates a web response, I would like to zip elements from several lists. However, the elements of the iterators are dict's. Therefore, I would like to fill up the missing values also with dict's, but each generated element should have it's own dict instance.

The following code groups elements from each list by itertools.zip_longest. As long as there is a non mutable fillvalue specified, there is no problem.

import collections
import itertools

l1 = [{"a": 100}, {"b": 200}, {"c": 300}]
l2 = [{"d": 400}]

ll = list(itertools.zip_longest(l1, l2, fillvalue=0))
print(ll)

-> [({'a': 100}, {'d': 400}), ({'b': 200}, 0), ({'c': 300}, 0)]

Now, when a mutable fillvalue is specified, all the fillvalue's share the same instance and so changing one, changes all:

import collections
import itertools

l1 = [{"a": 100}, {"b": 200}, {"c": 300}]
l2 = [{"d": 400}]

ll = list(itertools.zip_longest(l1, l2, fillvalue=dict()))
ll[1][1]["x"] = 150
print(ll)

-> [({'a': 100}, {'d': 400}), ({'b': 200}, {'x': 150}), ({'c': 300}, {'x': 150})]

To prevent that all the dicts share the same instance I used copy.deepcopy:

import collections
import copy
import itertools

l1 = [{"a": 100}, {"b": 200}, {"c": 300}]
l2 = [{"d": 400}]

ll = list(itertools.zip_longest(l1, l2, fillvalue=copy.deepcopy(dict())))
ll[1][1]["x"] = 150
print(ll)

-> [({'a': 100}, {'d': 400}), ({'b': 200}, {'x': 150}), ({'c': 300}, {'x': 150})]

As a result, still all dict's from the fillvalue share the same instance.

I would like to add that ll = [item or dict() for item in itertools.zip_longest(l1, l2)] works neither, assuming a fillvalue of None.

So, how can I make each fillvalue unique?


Solution

  • You can use a sentinel value and wrap zip_longest in another pipeline.

    For example:

    sentinel = object()
    l = list(tuple({} if x is sentinel else x for x in items) for items in zip_longest(l1, l2, fillvalue=sentinel))
    

    For this example, you can probably use None rather than a custom sentinel object.