10
\$\begingroup\$

I have a dictionary in Python (uglyDict) that has some really un-cool key names. I would like to rename them based on values from another dictionary (keyMapping). Here's an example of the dictionaries:

uglyDict = {
    "ORDER_NUMBER": "6492",
    "Ship To - Name": "J.B Brawls",
    "Ship To - Address 1": "42 BAN ROAD",
    "Ship To - City": "Jimville",
    "Ship To - State": "VA",
    "Ship To - Postal Code": "42691"
}

keyMapping = {
    "Market - Store Name": "WSCUSTOMERID",
    "Ship To - Name": "ShipToCompany",
    "Ship To - Country": "ShipToCountryCode",
    "Ship To - Postal Code": "ShipToZipcode",
    "Ship To - City": "ShipToCity",
    "Date - Order Date": "OrderDate",
    "Gift - Message": "SpecialInstructions",
    "Customer Email": "ShipToEmail",
    "Ship To - Address 2": "ShipToAddress2",
    "Ship To - Address 1": "ShipToAddress1",
    "Ship To - Phone": "ShipToPhoneNum",
    "Order - Number": "ORDER_NUMBER",
    "Ship To - State": "ShipToState",
    "Carrier - Service Selected": "ShipMethod",
    "Item - SKU": "PRODUCT_NUMBER",
    "Item - Qty": "Quantity"
}

Here is my code so far to rename uglyDict's keys:

prettyDict = {}
for mkey, mval in keyMapping.items():
    for ukey in uglyDict.keys():
        if mkey == ukey:
            prettyDict[mval] = uglyDict[mkey]

print(prettyDict)

The code works as desired, and prints the dictionary with renamed keys as shown below:

{'ORDER_NUMBER': '6492', 'ShipToCompany': 'J.B Brawls', 'ShipToAddress1': '42 BAN ROAD', 'ShipToCity': 'Jimville', 'ShipToState': 'VA', 'ShipToZipcode': '42691'}

My question is, is there any more efficient/more Pythonic way to do this? Preferably one where I don't have to loop over the keys of both dictionaries. I am using this code with much larger dictionaries and performance is needed.
Any insight is welcome!

\$\endgroup\$

2 Answers 2

19
\$\begingroup\$

I'd use a dict comprehension:

pretty_dict = {replacement_keys[k]: v for k, v in ugly_dict.items()}

This throws an error if replacement_keys (keyMapping) is missing any k. You might want to handle that with a default that falls back to the original key:

pretty_dict = {replacement_keys.get(k, k): v for k, v in ugly_dict.items()}

Time complexity is linear, assuming constant time dict lookups.

The main point of dicts is fast lookups, not iteration, so alarm bells should sound if you find yourself doing nested loops over multiple dicts.


Style suggestions:

  • Use snake_case rather than camelCase per PEP-8.
  • Generally avoid appending the type to every variable, users_count, source_string, names_list, translation_dict and so forth, although I assume this is for illustrative purposes here.
  • .keys() is superfluous as far as I know, but then again it doesn't hurt. You shouldn't need to loop over keys on a dict often.
\$\endgroup\$
6
  • 3
    \$\begingroup\$ This is a very nice solution, thank you! I love it when Python comprehension can be used. \$\endgroup\$ Commented Jul 9, 2021 at 15:12
  • \$\begingroup\$ Although when I said more Pythonic I wasn't asking about pothole case 😬 \$\endgroup\$ Commented Jul 9, 2021 at 15:13
  • 7
    \$\begingroup\$ @NoahBroyles regardless of whether you consider it pothole case and abominable it is the recommended standard for Python code. Style suggestions are important and good on ggorlen for providing them! \$\endgroup\$ Commented Jul 10, 2021 at 8:09
  • \$\begingroup\$ I agree -- most coding style decisions are non-decisions. When in Rome, do as the Romans. Otherwise, you raise cognitive load for people reading the code and your linter won't shush. Older libraries like BeautifulSoup have gone so far as to rewrite their camelCased API to pothole_case, so now they have 2 functions per name: legacy findAll and idiomatic find_all. When I see Python code with camelCase, 99% of the time it's low-quality in other regards. \$\endgroup\$
    – ggorlen
    Commented Jul 10, 2021 at 17:16
  • \$\begingroup\$ @NoahBroyles Pythonic means following the idioms of Python. Of which PEP 8 is a large part of the culture for many people. Another notable one is PEP 20 (import this). In the future, please be more clear when using terms in non-standard ways. \$\endgroup\$
    – Peilonrayz
    Commented Jul 11, 2021 at 0:06
2
\$\begingroup\$

The point of dictionaries is that lookup is fast, but you are not using that even though your keyMapping already is a dictionary. Let us look at your code.

prettyDict = {}
for mkey, mval in keyMapping.items():
    for ukey in uglyDict.keys():
        if mkey == ukey:
            prettyDict[mval] = uglyDict[mkey]

Even if uglyDict is small, you iterate over all element of the key mapping. This seems to be a bad starting point, so let us reverse the two loops.

prettyDict = {}
for ukey in uglyDict.keys():
    for mkey, mval in keyMapping.items():
        if mkey == ukey:
            prettyDict[mval] = uglyDict[mkey]

In the last line, mkey equals ukey, so we can change that to uglyDict[ukey], and of course you know how to avoid that lookup altogether:

prettyDict = {}
for ukey, uval in uglyDict.items():
    for mkey, mval in keyMapping.items():
        if mkey == ukey:
            prettyDict[mval] = uval

Let us now concentrate on the middle part:

    for mkey, mval in keyMapping.items():
        if mkey == ukey:

Here we look for the value of ukey in keyMapping, but surely that is what dictionaries are for and we don't have to iterate over all items to do so.

prettyDict = {}
for ukey, uval in uglyDict.items():
    if ukey in keyMapping:
        mval = keyMapping[ukey]
        prettyDict[mval] = uval

This is much better. From here, we can reformulate this using a dictionary comprehension like in ggorien's answer, if you prefer that.

prettyDict = {
    keyMapping[ukey]: uval
    for ukey, uval in uglyDict.items()
    if ukey in keyMapping
}

More importantly, you should decide how to handle the case that ukey is not in keyMapping. (Your example seems to have that got wrong with ORDER_NUMBER, btw.) If this would be a error, just omit the if ukey in keyMapping and handle the exception elsewhere. Or maybe you would like to keep the original key in that case:

prettyDict = {
    keyMapping.get(ukey, ukey): uval
    for ukey, uval in uglyDict.items()
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.