如果你还在想什么!你不会孤单,其实没那么复杂,让我解释一下。
如何仅使用内置函数将列表转换为字典
我们希望将以下列表转换为字典,使用奇数条目(从 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函数,one of the built-in functions,手册中有解释:
返回一个元组列表,其中第 i 个元组包含来自每个参数的第 i 个元素
换句话说,如果我们可以将列表分为两个列表a, c, e 和b, d,那么zip 将完成剩下的工作。
切片表示法
Slicings,我们看到它与Strings 一起使用,在List section 中也更进一步,它主要使用 range 或 short slice 表示法,但这就是long slice 符号的样子以及我们可以通过 step 完成的工作:
>>> 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 的原因。
石斑鱼食谱
虽然 grouper 被解释为 itertools 模块的一部分,但它也可以与基本功能完美配合。
一些严肃的巫术,对吗? =) 但实际上不过是香料的一点语法糖,石斑鱼配方是通过以下表达式完成的。
*[iter(l)]*2
如果有意义的话,这或多或少会转换为包含在列表中的同一迭代器的两个参数。让我们将其分解以帮助阐明。
压缩到最短
>>> 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 首先从其中获取一个键,然后是一个值,一个键和一个值,每次步进同一个迭代器来完成我们对切片所做的工作效率更高。
您可以通过以下带有较小的 What the? 因素来完成相同的操作。
>>> it = iter(l)
>>> dict(zip(it, it))
{'a': 'b', 'c': 'd'}
如果您注意到空键 e 已从所有示例中丢失,那该怎么办,这是因为 zip 选择了两个参数中最短的一个,那么我们该怎么办。
一种解决方案可能是向奇数长度的列表添加一个空值,您可以选择使用append 和if 语句来解决问题,尽管有点无聊,对吧?
>>> 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() function 而不是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 万条记录。
$ 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
什么都看不见! =)
开心!