如何将嵌套的OrderedDict转换为dict?

29 投票
7 回答
26167 浏览
提问于 2025-04-18 15:27

我有一个嵌套的 OrderedDict,我想把它转换成一个普通的 dict。但是用 dict() 转换的时候,似乎只转换了最外层的最后一个条目。

from collections import OrderedDict

od = OrderedDict(
    [
        (u'name', u'Alice'),
        (u'ID', OrderedDict(
            [
                (u'type', u'card'),
                (u'nr', u'123')
            ]
        )),
        (u'name', u'Bob'),
        (u'ID', OrderedDict(
            [
                (u'type', u'passport'),
                (u'nr', u'567')
            ]
        ))
    ]
)

print(dict(od))

输出结果:

{u'name': u'Bob', u'ID': OrderedDict([(u'type', u'passport'), (u'nr', u'567')])}

有没有什么直接的方法可以把里面所有的内容都转换过来呢?

7 个回答

0

我写了一个递归的方法,用来把一个 OrderedDict 转换成一个普通的字典(dict)。

def recursive_ordered_dict_to_dict(ordered_dict):
    simple_dict = {}

    for key, value in ordered_dict.items():
        if isinstance(value, OrderedDict):
            simple_dict[key] = recursive_ordered_dict_to_dict(value)
        else:
            simple_dict[key] = value

    return simple_dict

注意:OrderedDictdict 通常可以互换使用,但我在用 pytest 进行类型检查时,发现了一个问题。

2

注意: 这个回答只部分正确,想了解为什么字典大小相同,可以查看 这个链接

原始回答

这个回答并没有解决转换的问题,而是更多地讲了需要做什么。

一个基本的假设是,OrderedDict 的大小是 Dict 的两倍,这个想法是错误的。看看这个:

import sys
import random
from collections import OrderedDict

test_dict = {}
test_ordered_dict = OrderedDict()

for key in range(10000):
    test_dict[key] = random.random()
    test_ordered_dict[key] = random.random()

sys.getsizeof(test_dict)
786712

sys.getsizeof(test_ordered_dict)
786712

实际上,它们的大小是一样的。

不过,操作所花的时间是不一样的。实际上,创建一个大字典(包含100到10000个键)要比创建一个包含相同键的 OrderedDict 快大约7到8倍。(这是通过在 ipython 中使用 %timeit 验证的)

import sys
import random
from collections import OrderedDict


def operate_on_dict(r):
    test_dict = {}
    for key in range(r):
        test_dict[key] = random.random()

def operate_on_ordered_dict(r):
    test_ordered_dict = OrderedDict()
    for key in range(r):
        test_ordered_dict[key] = random.random()

%timeit for x in range(100): operate_on_ordered_dict(100)
100 loops, best of 3: 9.24 ms per loop

%timeit for x in range(100): operate_on_dict(100)
1000 loops, best of 3: 1.23 ms per loop

所以,我认为你应该直接把数据读入一个 dict 中并进行操作,而不是先创建一个 OrderedDict,然后再反复转换成 dict。

7

这个应该可以用:

import collections

def deep_convert_dict(layer):
    to_ret = layer
    if isinstance(layer, collections.OrderedDict):
        to_ret = dict(layer)

    try:
        for key, value in to_ret.items():
            to_ret[key] = deep_convert_dict(value)
    except AttributeError:
        pass

    return to_ret

不过,正如jonrsharpe提到的,其实这样做可能没什么必要,因为一个OrderedDict(按照设计)在任何需要dict的地方都能正常工作。

45

最简单的解决办法就是使用 json 的 dumps 和 loads 方法。

from json import loads, dumps
from collections import OrderedDict

def to_dict(input_ordered_dict):
    return loads(dumps(input_ordered_dict))

注意:上面的代码适用于那些被 json 认为可以序列化的字典。默认的对象类型列表可以在这里找到。

所以,如果有序字典里没有特殊值,这样的做法就足够了。

编辑:根据评论的反馈,我们来改进一下上面的代码。假设 input_ordered_dict 可能包含一些自定义类的对象,这些对象默认情况下是不能被 json 序列化的。在这种情况下,我们应该使用 json.dumpsdefault 参数,配合我们自己的自定义序列化器。

(例如):

from collections import OrderedDict as odict
from json import loads, dumps

class Name(object):
    def __init__(self, name):
        name = name.split(" ", 1)
        self.first_name = name[0]
        self.last_name = name[-1]

a = odict()
a["thiru"] = Name("Mr Thiru")
a["wife"] = Name("Mrs Thiru")
a["type"] = "test" # This is by default serializable

def custom_serializer(obj):
    if isinstance(obj, Name):
        return obj.__dict__

b = dumps(a) 
# Produces TypeError, as the Name objects are not serializable
b = dumps(a, default=custom_serializer)
# Produces desired output

这个例子可以扩展到更大的范围。我们甚至可以添加过滤器或者根据需要修改值。只需在 custom_serializer 函数中添加一个 else 部分。

def custom_serializer(obj):
    if isinstance(obj, Name):
        return obj.__dict__
    else:
        # Will get into this if the value is not serializable by default 
        # and is not a Name class object
        return None

在使用自定义序列化器的情况下,最上面给出的函数应该是:

from json import loads, dumps
from collections import OrderedDict

def custom_serializer(obj):
    if isinstance(obj, Name):
        return obj.__dict__
    else:
        # Will get into this if the value is not serializable by default 
        # and is also not a Name class object
        return None

def to_dict(input_ordered_dict):
    return loads(dumps(input_ordered_dict, default=custom_serializer))
3

你可以利用Python自带的copy机制来实现复制。

如果你想改变OrderedDict的复制行为,可以通过Python的copyreg模块来实现(这个模块也被pickle使用)。然后,你可以使用Python自带的copy.deepcopy()函数来进行转换。

import copy
import copyreg
from collections import OrderedDict

def convert_nested_ordered_dict(x):
    """
    Perform a deep copy of the given object, but convert
    all internal OrderedDicts to plain dicts along the way.

    Args:
        x: Any pickleable object

    Returns:
        A copy of the input, in which all OrderedDicts contained
        anywhere in the input (as iterable items or attributes, etc.)
        have been converted to plain dicts.
    """
    # Temporarily install a custom pickling function
    # (used by deepcopy) to convert OrderedDict to dict.
    orig_pickler = copyreg.dispatch_table.get(OrderedDict, None)
    copyreg.pickle(
        OrderedDict,
        lambda d: (dict, ([*d.items()],))
    )
    try:
        return copy.deepcopy(x)
    finally:
        # Restore the original OrderedDict pickling function (if any)
        del copyreg.dispatch_table[OrderedDict]
        if orig_pickler:
            copyreg.dispatch_table[OrderedDict] = orig_pickler

仅仅通过使用Python自带的复制机制,这个解决方案在以下几个方面优于这里的其他答案:

  • 不仅适用于JSON数据。

  • 不需要为每种可能的元素类型(比如listtuple等)实现特殊逻辑。

  • deepcopy()能够正确处理集合中的重复引用:

    x = [1,2,3]
    d = {'a': x, 'b': x}
    assert d['a'] is d['b']
    
    d2 = copy.deepcopy(d)
    assert d2['a'] is d2['b']
    

    因为我们的解决方案是基于deepcopy()的,所以我们也会有同样的优势。

  • 这个解决方案还可以转换恰好是OrderedDict属性,而不仅仅是集合元素:

    class C:
        def __init__(self, a):
            self.a = a
    
        def __repr__(self):
            return f"C(a={self.a})"
    
    c = C(OrderedDict([(1, 'one'), (2, 'two')]))
    print("original: ", c)
    print("converted:", convert_nested_ordered_dict(c))
    
    original:  C(a=OrderedDict([(1, 'one'), (2, 'two')]))
    converted: C(a={1: 'one', 2: 'two'})
    

撰写回答