web-dev-qa-db-ja.com

Pythonネストされた辞書のキーの文字を再帰的に置き換えますか?

ネストされた辞書のキーのドットを置き換える汎用関数を作成しようとしています。 3レベルの深さになる非ジェネリック関数がありますが、このジェネリックを実行する方法が必要です。どんな助けでもありがたいです!これまでの私のコード:

output = {'key1': {'key2': 'value2', 'key3': {'key4 with a .': 'value4', 'key5 with a .': 'value5'}}} 

def print_dict(d):
    new = {}
    for key,value in d.items():
        new[key.replace(".", "-")] = {}
        if isinstance(value, dict):
            for key2, value2 in value.items():
                new[key][key2] = {}
                if isinstance(value2, dict):
                    for key3, value3 in value2.items():
                        new[key][key2][key3.replace(".", "-")] = value3
                else:
                    new[key][key2.replace(".", "-")] = value2
        else:
            new[key] = value
    return new

print print_dict(output)

更新:私自身の質問に答えるために、json object_hooks:を使用して解決策を作りました

import json

def remove_dots(obj):
    for key in obj.keys():
        new_key = key.replace(".","-")
        if new_key != key:
            obj[new_key] = obj[key]
            del obj[key]
    return obj

output = {'key1': {'key2': 'value2', 'key3': {'key4 with a .': 'value4', 'key5 with a .': 'value5'}}}
new_json = json.loads(json.dumps(output), object_hook=remove_dots) 

print new_json
27
Bas Tichelaar

はい、より良い方法があります:

def print_dict(d):
    new = {}
    for k, v in d.iteritems():
        if isinstance(v, dict):
            v = print_dict(v)
        new[k.replace('.', '-')] = v
    return new

(編集:再帰です。詳細は Wikipedia を参照してください。)

28
horejsek

@horejsekのコードを使用しましたが、リストと文字列を置き換える関数を含むネストされた辞書を受け入れるようにコードを調整しました。

解決すべき同様の問題がありました:キャメルケースの規則の代わりにアンダースコアの小文字の規則のキーを置き換えたかったし、その逆も同じでした。

def change_dict_naming_convention(d, convert_function):
    """
    Convert a nested dictionary from one convention to another.
    Args:
        d (dict): dictionary (nested or not) to be converted.
        convert_function (func): function that takes the string in one convention and returns it in the other one.
    Returns:
        Dictionary with the new keys.
    """
    new = {}
    for k, v in d.iteritems():
        new_v = v
        if isinstance(v, dict):
            new_v = change_dict_naming_convention(v, convert_function)
        Elif isinstance(v, list):
            new_v = list()
            for x in v:
                new_v.append(change_dict_naming_convention(x, convert_function))
        new[convert_function(k)] = new_v
    return new
6
jllopezpino

ネストされたリストと辞書を処理する単純な再帰的なソリューションを次に示します。

def change_keys(obj, convert):
    """
    Recursivly goes through the dictionnary obj and replaces keys with the convert function.
    """
    if isinstance(obj, dict):
        new = {}
        for k, v in obj.iteritems():
            new[convert(k)] = change_keys(v, convert)
    Elif isinstance(obj, list):
        new = []
        for v in obj:
            new.append(change_keys(v, convert))
    else:
        return obj
    return new
6
ngenain

実際には、すべての回答に間違いが含まれており、結果の入力ミスにつながる可能性があります。

@ngenainの答えを取り入れて、少し下で改善します。

私の解決策は、dictから派生した型(OrderedDictdefaultdictなど)と、listだけでなくsetおよびTupleタイプ。

また、関数の冒頭で最も一般的な型の単純な型チェックを実行して、比較数を減らします(大量のデータで少し速度が上がる可能性があります)。

Python 3.で動作します。3. Py2のobj.items()obj.iteritems()に置き換えます。

def change_keys(obj, convert):
    """
    Recursively goes through the dictionary obj and replaces keys with the convert function.
    """
    if isinstance(obj, (str, int, float)):
        return obj
    if isinstance(obj, dict):
        new = obj.__class__()
        for k, v in obj.items():
            new[convert(k)] = change_keys(v, convert)
    Elif isinstance(obj, (list, set, Tuple)):
        new = obj.__class__(change_keys(v, convert) for v in obj)
    else:
        return obj
    return new

私がニーズを正しく理解している場合、ほとんどのユーザーはキーを変換して、キー名にドットを使用できないmongoDBでそれらを使用したいと考えています。

5
baldr

元のキーを削除する必要がありますが、ループの本体で実行することはできません。反復中にRunTimeError:ディクショナリのサイズが変更されるためです。

これを解決するには、元のオブジェクトのa copyを反復処理しますが、元のオブジェクトを変更します。

def change_keys(obj):
    new_obj = obj
    for k in new_obj:
            if hasattr(obj[k], '__getitem__'):
                    change_keys(obj[k])
            if '.' in k:
                    obj[k.replace('.', '$')] = obj[k]
                    del obj[k]

>>> foo = {'foo': {'bar': {'baz.121': 1}}}
>>> change_keys(foo)
>>> foo
{'foo': {'bar': {'baz$121': 1}}}
2
bk0

Jllopezpinoの答えは機能しますが、最初は辞書に限定されていますが、元の変数で機能するものはリストまたは辞書です。

def fix_camel_cases(data):
    def convert(name):
        # https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
        s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
        return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

    if isinstance(data, dict):
        new_dict = {}
        for key, value in data.items():
            value = fix_camel_cases(value)
            snake_key = convert(key)
            new_dict[snake_key] = value
        return new_dict

    if isinstance(data, list):
        new_list = []
        for value in data:
            new_list.append(fix_camel_cases(value))
        return new_list

    return data
0
James Lin

すべてをJSONにダンプして文字列全体を置き換え、JSONをロードし直すことができます

def nested_replace(data, old, new):
    json_string = json.dumps(data)
    replaced = json_string.replace(old, new)
    fixed_json = json.loads(replaced)
    return fixed_json

またはワンライナーを使用

def short_replace(data, old, new):
    return json.loads(json.dumps(data).replace(old, new))
0
Ariel Voskov

以下は、好意的な人のためにdict内包表記を使用した@horejsekの回答の1行の変形です。

def print_dict(d):
    return {k.replace('.', '-'): print_dict(v) for k, v in d.items()} if isinstance(d, dict) else d

私はこれをPython 2.7でのみテストしました

0
ecoe