递归地添加新的 k:v 对到嵌套字典

recursively add new k:v pair to nested dictionary

我有一个嵌套字典,其中任何级别我都可能有国家/地区信息。我正在应用一个从名为 to_code() 的库中获取的国家/地区规范化函数,它吐出传递的字符串的 ISO 代码。

这是我的函数:

def update_country(dictionary):    
   for k, v in dictionary.items():
       if 'country' in k:
           dictionary[k+"_iso"] = to_code(v, fuzzy=True)
           
       elif isinstance(v, dict):
           for result in update_country(dictionary=v):
               yield result
               
       elif isinstance(v, list):
           for d in v:
               if isinstance(d, dict):
                   for result in update_country(dictionary=d):
                       yield result

想象一下这个嵌套 JSON

JSON = {"company_code": "123456",
        "name": "Astrocom AG",
        "officers": [{"name": "Abigail Kaloomp",
                      "country_of_residence": "bvi"},
                     {"name": "EXPONET limited",
                      "address": {"address_line_1": "somewhere",
                                  "address_line_2": "somewhere_else",
                                  "country": "united kingdom"}}],
        "address": {"address_line_1": "somewhere",
                    "address_line_2": "somewhere_else",
                    "country": "italia"}}

当我运行

from pprint import pprint
list(update_country(JSON))
pprint(JSON)

我收到以下错误:

Traceback (most recent call last):
  File "/home/gabri/.local/lib/python3.8/site-packages/IPython/core/interactiveshell.py", line 3417, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-39-4ac13e957742>", line 1, in <module>
    list(update_country(JSON))
  File "<ipython-input-38-8f467b1cdf1b>", line 7, in update_country
    for result in update_country(dictionary=v):
  File "<ipython-input-38-8f467b1cdf1b>", line 2, in update_country
    for k, v in dictionary.items():
RuntimeError: dictionary changed size during iteration

据我了解 - 事实上,如果我没有尝试插入新密钥,而是通过替换以下两行来更新现有密钥:

           dictionary[k+"_iso"] = to_code(v, fuzzy=True)
           # becomes
           dictionary[k] = to_code(v, fuzzy=True)  # overwrite existing value

然后我确实得到一个 JSON 国家/地区键我有一个 ISO 代码。

问题是我两者都需要。使用此逻辑是否可行,或者在迭代期间无法更改字典?

您可以像这样拆分迭代:

  1. 创建所有新键(字典理解为 {f"{k}_iso": v for k, v in dictionary.items() if "country" in k}
  2. 用第 1 步的结果更新你的字典
  3. 仅在产生结果的条件下执行 for 循环

您可以使用此示例递归更改字典:

JSON = {"company_code": "123456",
        "name": "Astrocom AG",
        "officers": [{"name": "Abigail Kaloomp",
                      "country_of_residence": "bvi"},
                     {"name": "EXPONET limited",
                      "address": {"address_line_1": "somewhere",
                                  "address_line_2": "somewhere_else",
                                  "country": "united kingdom"}}],
        "address": {"address_line_1": "somewhere",
                    "address_line_2": "somewhere_else",
                    "country": "italia"}}

def abbr(country_name):
    if country_name == 'italia':
        return 'IT'
    elif country_name == 'united kingdom':
        return 'UK'
    else:
        return '?'


def update_country(d):
    if isinstance(d, dict):
        rv = {}
        for k, v in d.items():
            if k == 'country':
                rv[k + '_iso'] = abbr(v)
            rv[k] = update_country(v)
        return rv
    elif isinstance(d, list):
        rv = [update_country(v) for v in d]
        return rv
    else:
        return d

new_JSON = update_country(JSON)

# pretty print the new dictionary:
import json
print(json.dumps(new_JSON, indent=4))

打印:

{
    "company_code": "123456",
    "name": "Astrocom AG",
    "officers": [
        {
            "name": "Abigail Kaloomp",
            "country_of_residence": "bvi"
        },
        {
            "name": "EXPONET limited",
            "address": {
                "address_line_1": "somewhere",
                "address_line_2": "somewhere_else",
                "country_iso": "UK",
                "country": "united kingdom"
            }
        }
    ],
    "address": {
        "address_line_1": "somewhere",
        "address_line_2": "somewhere_else",
        "country_iso": "IT",
        "country": "italia"
    }
}