如何将嵌套的OrderedDict转换为字典?

29

我有一个嵌套的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')])}

有没有一种直接的方法可以转换所有出现的情况?


你想要仅转换 OrderedDict 实例吗? - Patrick Collins
为什么你想要转换它?你可以在几乎任何dict可用的地方使用OrderedDict - jonrsharpe
@PatrickCollins:抱歉,我不理解你的问题。我希望将所有的OrderedDict转换为dicts,对于所有的元素(我在几秒钟前澄清了这一点)。 - WoJ
@jonrsharpe:我将获得一个巨大的OrderedDict(数百兆到几个吉字节),我读到内存开销很大(大约是两倍)。由于我不需要顺序,我至少会削减它以保持可管理性。 - WoJ
7个回答

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))

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

因此,如果有序字典不包含特殊值,则应该足够了。

编辑:根据评论,让我们改进上面的代码。假设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))

5
如果字典中的任何对象未正确定义 repr,则此处代码将会出错。 - Patrick Collins
1
如果字典中包含任何构造函数不在您的作用域内的对象,或者包含构造函数在您的作用域内但名称不同的对象,则程序也会出错。 - Patrick Collins
如果您的字典包含无法进行JSON序列化的项目,这也会导致错误。 - Paul T.

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的使用场景中进行操作。


3
谢谢 - 这对我的示例有效。我需要考虑如何处理字典列表(我发现在获取的数据中也有这种情况)。 - WoJ
@WoJ 这个解决方案适用于任何类型的嵌套可迭代对象,无论它们是不是字典。 - Patrick Collins
@PatrickCollins 他们想要递归遍历字典列表。 - jberryman

3

你应该利用Python内置的copy机制。

通过Python的copyreg模块(也被pickle使用)可以重写OrderedDict的复制行为。然后,你可以使用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内置的复制基础设施,这个解决方案在以下方面优于此处呈现的所有其他答案:
  • Works for more than just JSON data.

  • Does not require you to implement special logic for each possible element type (e.g. list, tuple, etc.)

  • deepcopy() will properly handle duplicate references within the collection:

    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']
    

    Since our solution is based on deepcopy() we'll have the same advantage.

  • This solution also converts attributes that happen to be OrderedDict, not only collection elements:

    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'})
    

2

注意: 这个回答只是部分正确的,查看https://dev59.com/TF8f5IYBdhLWcg3wFfT_#25057250了解更多为什么字典大小相同。

原始回答

这并没有回答关于转换的问题,而是更多关于需要做什么。

有序字典比字典大一倍的基本假设是错误的。检查这个:

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


有趣。我基于另一个SO答案得出了我的观点,该答案指出_(OrderedDict)的速度不会慢很多,但是至少会使内存使用量增加一倍以上,而使用普通的dict_。尽管如此,由于OrderedDict是从我无法控制的函数返回的,因此我没有选择余地。 - WoJ
@WoJ 轮到我说有趣的事情了 :) 那个答案来自编写TimSort的Tim Peters,我现在真的很困惑。已经在同一个问题上提出了一个问题 - Anshul Goyal

0
我编写了一个递归方法,将一个有序字典 OrderedDict 转换成简单字典。
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

注意:`OrderedDict` 和 `dict` 通常是可以互换的,但是当我在使用 `pytest` 运行两种类型之间的 `assert` 时遇到了问题。

0

这段代码应该可以处理嵌套列表。

def nested_convert_to_dict(input: [dict, collections.OrderedDict]):
    if isinstance(input, collections.OrderedDict):
        res = dict(input)
    else:
        res = input
    try:
        for key, value in res.items():
            res[key] = nested_convert_to_dict(value)
            if isinstance(value, list):
                new_value = []
                for item in value:
                    if isinstance(item, collections.OrderedDict):
                        item = nested_convert_to_dict(item)
                    new_value.append(item)
                res[key] = new_value
    except AttributeError:
        pass
    return res

0

这里有一个版本,也可以处理列表和元组。在评论中,OP提到了还需要处理字典列表的情况。

请注意,这个版本也将元组转换为列表。保留元组则留给读者自己练习 :)

def od2d(val):                                                                  
  if isinstance(val, (OrderedDict, dict)):                                    
      return {k: od2d(v) for k, v in val.items()}                             
  elif isinstance(val, (tuple, list)):                                        
      return [od2d(v) for v in val]                                           
  else:                                                                       
      return val 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接