1

I'm writing a function to turn a list of dictionaries to a dict of dictionaries. I've since found Find the index of a dict within a list, by matching the dict's value has a better solution because it deals with potential duplicate values by adding an index item rather than pulling from the dicts.

However, in testing I found that the function alters the original list even though I'm making an explicit copy. This is obvious if I attempt to re-run the fuction, but I cannot see why.

def lod2dict(iterable,target="name"): 
    '''Turn a list of dictionaries into a dict of dicts.
    If a dict does not have the target key, that element is skipped.'''
    d = {}
    copy = list(iterable) 
    # N.B. It makes no difference whether I use this or
    #copy = list[:]
    # or
    #copy = [x for x in iterable]

    for item in copy:
        try:
            newkey=item[target]
            item.pop(target)
            d[newkey] = item
        except KeyError:
            print("Failure for", item)
            pass
    return d

Here's a run showing the destructive effect with a small dict.

testcase = {"heroes": [
        {"name": "Batman", "home": "Wayne Manor", "weapon": "Batarang (TM)"},
        {"name": "Spider-Man", "home": "Queens", "weapon": "Sarcasm"},
        {"name": "Deadpool", "home": "Good Question. Next?", "weapon": "Sarcasm and swords"},
        {"name": "Wonder Woman", "home": "Themyscira", "weapon": "Lasso and sword"},
        {"name": "Aquaman", "home": "The Oceans", "weapon": "trident"}
    ]}

In [4]: lod2dict(testcase['heroes'], 'name')
Out[4]:
{'Aquaman': {'home': 'The Oceans', 'weapon': 'trident'},
 'Batman': {'home': 'Wayne Manor', 'weapon': 'Batarang (TM)'},
 'Deadpool': {'home': 'Good Question. Next?', 'weapon': 'Sarcasm and swords'},
 'Spider-Man': {'home': 'Queens', 'weapon': 'Sarcasm'},
 'Wonder Woman': {'home': 'Themyscira', 'weapon': 'Lasso and sword'}}

In [5]: lod2dict(testcase['heroes'], 'name')
Failure for {'home': 'Wayne Manor', 'weapon': 'Batarang (TM)'}
Failure for {'home': 'Queens', 'weapon': 'Sarcasm'}
Failure for {'home': 'Good Question. Next?', 'weapon': 'Sarcasm and swords'}
Failure for {'home': 'Themyscira', 'weapon': 'Lasso and sword'}
Failure for {'home': 'The Oceans', 'weapon': 'trident'}
Out[5]: {}
Rache
  • 236
  • 2
  • 11
  • 1
    Those make shallow copies. They copy the list structure, but don't make copies of the contents. You need to make a deep copy by the sounds of it. `item` is a reference to the content of the old list, so mutating it by `pop`ping will alter the old list contents. – Carcigenicate Nov 02 '17 at 16:50
  • Aha. I see the issue there. Thanks, Carcigenicate! – Rache Nov 02 '17 at 16:54
  • Np. I'm glad I don't deal with mutable objects anymore. Stuff like this gives me headaches and frustration flashbacks just thinking about it. – Carcigenicate Nov 02 '17 at 16:55
  • I had a little flash--and tested--that copying the dict inside the try block would work and allow me to pop the name element, yielding the result I was originally looking for. – Rache Nov 04 '17 at 02:28

0 Answers0