将列表转换为字典

105
l = ["a", "b", "c", "d", "e"]
我想将这个列表转换为字典,格式如下:
d = {"a": "b", "c": "d", "e": ""}

基本上,偶数将成为键,而奇数将成为值。我知道可以使用带有if语句的for循环等“非pythonic”的方法来完成它,但我相信应该有一种更“pythonic”的方法来实现这一目标。所以,我感谢任何帮助 :)


将列表成员分别作为其自己的“键”和“值”:dict(zip(list, list)) - DanielBell99
4个回答

171

如果您还在思考什么,那么您并不孤单,实际上它并不是那么复杂,请让我解释一下。

如何只使用内置函数将列表转换为字典

我们希望使用奇数项(从1开始计数)作为键,将其连续的偶数项映射到以下列表中,以将其转换为字典。

l = ["a", "b", "c", "d", "e"]

dict()

使用内置的dict函数可以创建字典,参考手册中支持以下方法:Mapping Types

dict(one=1, two=2)
dict({'one': 1, 'two': 2})
dict(zip(('one', 'two'), (1, 2)))
dict([['two', 2], ['one', 1]])

最后一个选项建议我们提供一个由2个值或(key, value)元组组成的列表,因此我们要将我们的顺序列表转换为:

l = [["a", "b"], ["c", "d"], ["e",]]

我们还介绍了zip函数,它是内置函数之一,手册中这样解释:

返回的是一个元组(tuple)列表,其中第 i 个元组包含来自每个参数的第 i 个元素

换句话说,如果我们可以将列表转换为两个列表 a, c, eb, d,那么 zip 就会完成剩下的工作。

切片符号

切片符号 在我们使用字符串以及后面的列表部分中看到,主要使用range短切片符号。但这就是长切片符号的样子,以及我们可以用步长实现的内容:

>>> l[::2]
['a', 'c', 'e']

>>> l[1::2]
['b', 'd']

>>> zip(['a', 'c', 'e'], ['b', 'd'])
[('a', 'b'), ('c', 'd')]

>>> dict(zip(l[::2], l[1::2]))
{'a': 'b', 'c': 'd'}

尽管这是理解其中涉及的机制最简单的方法,但有一个缺点,因为每次切片都是新的列表对象,就像在这个克隆示例中所看到的那样:

>>> a = [1, 2, 3]
>>> b = a
>>> b
[1, 2, 3]

>>> b is a
True

>>> b = a[:]
>>> b
[1, 2, 3]

>>> b is a
False

虽然b看起来像a,但现在它们是两个独立的对象,这就是为什么我们更喜欢使用grouper recipe的原因。

分组方法

尽管分组器被解释为itertools模块的一部分,但它也可以很好地与基本函数配合使用。

有些真正的巫术对吧?=)但实际上,分组器只是一点语法糖,通过以下表达式实现了分组方法。

*[iter(l)]*2

这更或少将相同迭代器的两个参数包装在列表中,如果有意义的话。让我们分解一下以帮助澄清。

zip用于最短

>>> l*2
['a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e']

>>> [l]*2
[['a', 'b', 'c', 'd', 'e'], ['a', 'b', 'c', 'd', 'e']]

>>> [iter(l)]*2
[<listiterator object at 0x100486450>, <listiterator object at 0x100486450>]

>>> zip([iter(l)]*2)
[(<listiterator object at 0x1004865d0>,),(<listiterator object at 0x1004865d0>,)]

>>> zip(*[iter(l)]*2)
[('a', 'b'), ('c', 'd')]

>>> dict(zip(*[iter(l)]*2))
{'a': 'b', 'c': 'd'}

正如您所看到的,两个迭代器的地址是相同的,因此我们正在使用相同的迭代器。zip先从中获取一个键,然后每次获取一个值和一个键,并通过步进相同的迭代器来完成我们使用切片所做的事情,从而实现更高效的操作。

您可以通过以下方式完成非常相似的操作,这可能具有更小的“什么?”因素。

>>> it = iter(l)     
>>> dict(zip(it, it))
{'a': 'b', 'c': 'd'}

如果您注意到所有的示例中都缺少空键e,那么它该怎么办呢?因为zip会选择两个参数中较短的一个,所以我们该怎么做呢。

嗯,一种解决方案可能是向奇数长度的列表添加一个空值。您可以选择使用appendif语句来完成这个技巧,尽管有点无聊,对吧?

>>> if len(l) % 2:
...     l.append("")

>>> l
['a', 'b', 'c', 'd', 'e', '']

>>> dict(zip(*[iter(l)]*2))
{'a': 'b', 'c': 'd', 'e': ''}

现在,在你摆脱去输入from itertools import izip_longest之前,你可能会惊讶地发现它是不必要的。我们可以只使用内置函数来实现同样的功能,而且我认为甚至更好。

最长的map

我更喜欢使用map()函数而不是izip_longest(),它不仅使用更短的语法,不需要导入,还可以在需要时自动赋予一个真正的None空值。

>>> l = ["a", "b", "c", "d", "e"]
>>> l
['a', 'b', 'c', 'd', 'e']

>>> dict(map(None, *[iter(l)]*2))
{'a': 'b', 'c': 'd', 'e': None} 

根据KursedMetal指出的,比较这两种方法的性能,很明显itertools模块在处理大量数据时远优于map函数,以1000万条记录为基准。

$ time python -c 'dict(map(None, *[iter(range(10000000))]*2))'
real    0m3.755s
user    0m2.815s
sys     0m0.869s
$ time python -c 'from itertools import izip_longest; dict(izip_longest(*[iter(range(10000000))]*2, fillvalue=None))'
real    0m2.102s
user    0m1.451s
sys     0m0.539s

然而,导入模块的成本对于较小的数据集来说是一个负担,在大约10万条记录开始头对头到达时,map()函数能更快地返回结果。

$ time python -c 'dict(map(None, *[iter(range(100))]*2))'
real    0m0.046s
user    0m0.029s
sys     0m0.015s
$ time python -c 'from itertools import izip_longest; dict(izip_longest(*[iter(range(100))]*2, fillvalue=None))'
real    0m0.067s
user    0m0.042s
sys     0m0.021s

$ time python -c 'dict(map(None, *[iter(range(100000))]*2))'
real    0m0.074s
user    0m0.050s
sys     0m0.022s
$ time python -c 'from itertools import izip_longest; dict(izip_longest(*[iter(range(100000))]*2, fillvalue=None))'
real    0m0.075s
user    0m0.047s
sys     0m0.024s

看起来很简单!=)

享受吧!


3
Python 3 不支持 map(None, ...),因此您的解决方案仅适用于 Python 2.X。 - laike9m
2
Python 3.4.3支持map(None, ...),例如dict(map(None, *[iter(l)]*2))对于l = ['a', 'b', 'c', 'd', 'e']返回dict(map(None, *[iter(l)]*2))。 - user4322779

53

使用通常的grouper配方,你可以这样做:

Python 2:

d = dict(itertools.izip_longest(*[iter(l)] * 2, fillvalue=""))

Python 3:

d = dict(itertools.zip_longest(*[iter(l)] * 2, fillvalue=""))

5
你也可以这样做:d = dict(itertools.izip_longest(l[::2], l[1::2], fillvalue='')),或者在Python 3中使用 d = dict(itertools.zip_longest(l[::2], l[1::2], fillvalue=''))。你的版本完全可行,但对于那些没有意识到[iter(l)] * 2创建了一个包含两个引用的列表,指向同一个迭代器的人可能会稍微有些困惑。([iter(l)]*2 -> [<list_iterator object at 0x01383FD0>, <list_iterator object at 0x01383FD0>] - JAB
@JAB: 由于分组食谱相当普遍,我认为使用l[::2]l[1::2]创建两个列表副本来证明你的方法并没有足够的好处。 - Sven Marnach
1
嗯,我一直以为切片是对象的视图,而不是实际的副本,这是由于在可变序列上使用 del x[:1]x[:] = [2, 3] 等操作,以及 numpy 将数组切片实现为视图。但实际上似乎并非如此,所以更有效的方法应该是 d = dict(izip_longest(islice(l, 0, None, 2), islice(l, 1, None, 2), fillvalue=''))。我想知道是否已经提交了这方面的功能请求,如果有的话,响应是什么... - JAB

21

我会选择使用递归:

l = ['a', 'b', 'c', 'd', 'e', ' ']
d = dict([(k, v) for k,v in zip (l[::2], l[1::2])])

4
为什么这是递归的?在我看来它像是迭代。 - user4322779
1
字典推导式是什么 https://www.python.org/dev/peps/pep-0274/ - Williams
3
我认为这个dict(zip(l[::2], l[1::2]))已经足够了。在我的Python 3.8.5中运行良好。 - Shabeer

2

我不确定这是否能帮到你,但对我有效:

l = ["a", "b", "c", "d", "e"]
outRes = dict((l[i], l[i+1]) if i+1 < len(l) else (l[i], '') for i in xrange(len(l)))

需要注意的是,+1 是从 1 开始计数,如果你的起始值是 0,只需删除 +1 即可。对我来说非常有效!太棒了! - Marco

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