将字典列表转换为命名元组列表的Pythonic方式

7 投票
4 回答
6905 浏览
提问于 2025-04-17 04:41

我有一个包含多个字典的 list,需要把它转换成一个包含命名元组(namedtuple,更好)或者简单元组(tuple)的 list,同时要把第一个变量按空格分开。

有什么更符合Python风格的方法吗?

我把我的代码简化了一些。欢迎使用列表推导式、生成器表达式和itertools。

输入数据:

dl = [{'a': '1 2 3',
       'd': '*',
       'n': 'first'},
      {'a': '4 5',
       'd': '*', 'n':
       'second'},
      {'a': '6',
       'd': '*',
       'n': 'third'},
      {'a': '7 8 9 10',
       'd': '*',
       'n': 'forth'}]

简单算法:

from collections import namedtuple

some = namedtuple('some', ['a', 'd', 'n'])

items = []
for m in dl:
    a, d, n = m.values()
    a = a.split()
    items.append(some(a, d, n))

输出结果:

[some(a=['1', '2', '3'], d='*', n='first'),
 some(a=['4', '5'], d='*', n='second'),
 some(a=['6'], d='*', n='third'),
 some(a=['7', '8', '9', '10'], d='*', n='forth')]

4 个回答

2

还有一个选项,不确定它比其他的好还是不好:

class some(namedtuple('some', 'a d n')):
    def __new__(cls, **args):
        args['a'] = args['a'].split()
        return super(some, cls).__new__(cls, **args)

items = list(some(**m) for m in dl)

顺便说一下,我并不是一定要让基类和子类叫同样的名字 some。我喜欢这样做,因为这样生成的类在转换成字符串时会用到名字 some,而且这通常没有给我带来麻烦。不过,如果你在调试的时候要看类名,这样可能会让人困惑。所以要小心使用。

或者用不同的方法实现同样的想法:

some = namedtuple('some', 'a d n')

def make_some(args):
    args = args.copy()
    args['a'] = args['a'].split()
    return some(**args)

items = map(make_some, dl) # NB: this doesn't return a list in Python 3
3

我想在这里说几句,因为我非常喜欢命名元组和字典!

这里有一个列表推导式,里面包含了一个字典推导式,可以帮你初步处理字典:

split_dictionaries = \ 
    [{key: value.split() for k, value in d.iteritems()} for d in dl] 

我经常使用一个我称之为“保鲜盒”的方法,它可以递归地把字典转换成命名元组。你可以在这个链接中找到代码。这里有一段简化的代码,方便我们在这里整合,能很干净地完成这个操作。

import collections

def namedtuple_from_mapping(mapping, name="Tupperware"):
    this_namedtuple_maker = collections.namedtuple(name, mapping.iterkeys())
    return this_namedtuple_maker(**mapping)

所以,给定这个函数,你可以这样做——我们稍后会对它进行重构:

split_namedtuples = [ 
    namedtuple_from_mapping(
        {key: value.split() for k, value in d.iteritems()}
    ) for d in dl
]   

现在,经过更好的封装和可读性提升后:

def format_string(string):
    return string.split()

def format_dict(d):
    return {key: format_string(value) for key, value in d.iteritems()}

formatted_namedtuples = [namedtuple_from_mapping(format_dict(d)) for d in dl]
7

下面,@Petr Viktorin 指出了我最初回答和你最初解决方案的问题:

警告!字典中的值是没有特定顺序的!如果这个解决方案有效,并且 a、d、n 确实按这个顺序返回,那只是巧合。如果你使用不同版本的 Python 或者以不同方式创建字典,这个结果可能就不成立了。

(我有点尴尬,居然一开始没注意到这一点,还因此得了 45 点声望!)

请使用 @eryksun 的建议:

items =  [some(m['a'].split(), m['d'], m['n']) for m in dl]

我最初的错误答案。除非你有一个 OrderedDict 的列表,否则不要使用它。

items =  [some(a.split(), d, n) for a,d,n in (m.values() for m in dl)]

撰写回答