如何将嵌套的OrderedDict转换为dict?
我有一个嵌套的 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 个回答
我写了一个递归的方法,用来把一个 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
注意:OrderedDict
和 dict
通常可以互换使用,但我在用 pytest
进行类型检查时,发现了一个问题。
注意: 这个回答只部分正确,想了解为什么字典大小相同,可以查看 这个链接。
原始回答
这个回答并没有解决转换的问题,而是更多地讲了需要做什么。
一个基本的假设是,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。
这个应该可以用:
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
的地方都能正常工作。
最简单的解决办法就是使用 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.dumps
的 default
参数,配合我们自己的自定义序列化器。
(例如):
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))
你可以利用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数据。
不需要为每种可能的元素类型(比如
list
、tuple
等)实现特殊逻辑。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'})