将嵌套命名元组的列表展开为字典列表

4

示例

import collections

A = collections.namedtuple('A', 'a b c')
B = collections.namedtuple('B', 'x y z w')

L = [A(a='CODE1',  b=B(x=2, y='u', z='v', w='a'), c=10),
     A(a='CODE2',  b=B(x=4, y='h', z='r', w='b'), c=30)]

我有一组命名元组,想要将其展平,并用b的值替换嵌套的B

我的尝试:

首先,我尝试将两个命名元组转换为字典并合并它们,但问题是在字典推导式中删除了原始的b值,并且顺序不正确:

t = [{k: v for k, v in {**x.b._asdict(), **x._asdict()}.items() 
      if k != 'b'} for x in L]
print (t)

[{'x': 2, 'y': 'u', 'z': 'v', 'w': 'a', 'a': 'CODE1', 'c': 10}, 
 {'x': 4, 'y': 'h', 'z': 'r', 'w': 'b', 'a': 'CODE2', 'c': 30}]

还有另一种解决方法,与需要的方法类似,但在我看来有点复杂。我更喜欢列表推导式的解决方案:

t = []
for x in L:
    out = {}
    d = x._asdict()
    for k, v in d.items():
        if isinstance(v, tuple):
            d1 = v._asdict()
            for k1, v1 in d1.items():
                out[k1] = v1
        else:
            out[k] = v
    t.append(out)

print (t)

[{'a': 'CODE1', 'x': 2, 'y': 'u', 'z': 'v', 'w': 'a', 'c': 10}, 
 {'a': 'CODE2', 'x': 4, 'y': 'h', 'z': 'r', 'w': 'b', 'c': 30}]
2个回答

3

使用复杂的 list 推导式和 OrderedDict 对象:

from collections import namedtuple, OrderedDict
from itertools import chain

...
res = [OrderedDict(chain.from_iterable(
                   v._asdict().items() if isinstance(v, (A, B)) else [(k, v)]
                   for k, v in nt._asdict().items())) for nt in L]
print(res)

输出结果:
[OrderedDict([('a', 'CODE1'),
              ('x', 2),
              ('y', 'u'),
              ('z', 'v'),
              ('w', 'a'),
              ('c', 10)]),
 OrderedDict([('a', 'CODE2'),
              ('x', 4),
              ('y', 'h'),
              ('z', 'r'),
              ('w', 'b'),
              ('c', 30)])]

“诀窍”在于获取传递给OrderedDict(...)的元组扁平序列(顶层元组被包装在[(k, v)]中以便与潜在的嵌套元组保持一致)。

那么输出的顺序是个问题吗? - jezrael
@jezrael,恐怕为了保持最原始的顺序,您需要坚持使用OrderedDict。请查看我的更新。 - RomanPerekhrest
没问题,OrderedDict 很好。 - jezrael

2
据我所知,字典中没有排序。如果排序对您很重要,您可以考虑使用来自Collections包的OrderedDict(请参阅手册)。顺便说一下,似乎namedtuple是基于OrderedDict构建的...

关于您的问题,也许您可以创建另一种类型的namedtuple以强制执行您的顺序:

AB = collections.namedtuple('AB', 'a x y z w c')

然后,您可以根据需要明确转码:
list(map( lambda l: AB(l.a, l.b.x, l.b.y, l.b.z, l.b.w, l.c) , L)) 

如果出于某种原因您不想显式地编写B的字段,您可以使用自动化来简化一些操作。
AB(L[0].a,*[v for v in L[0].b._asdict()],L[0].c)

编辑以嵌入注释和对注释的回答。
import collections
from itertools import chain

A = collections.namedtuple('A', 'a b c')
B = collections.namedtuple('B', 'x y z w')

L = [A(a='CODE1',  b=B(x=2, y='u', z='v', w='a'), c=10),
     A(a='CODE2',  b=B(x=4, y='h', z='r', w='b'), c=30)]

AB = collections.namedtuple('AB',
" ".join(chain.from_iterable(
        v._asdict().keys() if isinstance(v, (A, B)) else [k] for k,v in L[0]._asdict().items()) 
    )
)
LL = [AB._make(dict(
    chain.from_iterable( 
        v._asdict().items() if isinstance(v, (A, B)) else [(k, v)] for k, v in nt._asdict().items())
    )) for nt in L] 
print (LL)

这是我现在能看到的所有内容,希望对你有所帮助。

解决方案看起来不错,但是有没有可能更加动态化,而不使用 a x y z w c - jezrael
结合RomanPerekhrest的非常优雅的解决方案:[AB._make(dict(chain.from_iterable( v._asdict().items() if isinstance(v, (A, B)) else [(k, v)] for k, v in nt._asdict().items()))) for nt in L] - Benjamin schwarz
好的,但是如何创建动态的 AB,而不需要输入 'a x y z w c' - jezrael
AB = collections.namedtuple('AB', " ".join(chain.from_iterable( v._asdict().keys() if isinstance(v, (A, B)) else [k] for k,v in L[0]._asdict().items()) ) ) - Benjamin schwarz

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