展示一些酷炫的 Python 列表推导式。

14

Python和其他几种(函数式)编程语言的主要优势之一是列表推导。它们允许程序员在一行代码中编写复杂表达式。虽然一开始可能会令人困惑,但如果一个人习惯了语法,它比嵌套复杂的for循环要好得多。

话虽如此,请与我分享一些列表推导的最酷用途。(通过酷,我只是指有用的)它可以用于某些编程竞赛或生产系统。

例如:要对矩阵mat进行转置

>>> mat = [
...        [1, 2, 3],
...        [4, 5, 6],
...        [7, 8, 9],
...       ]

>>> [[row[i] for row in mat] for i in [0, 1, 2]]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

请提供表达式的描述以及使用场景(如果可能的话)。

2
你应该将这个标记为社区 Wiki(编辑你的问题并勾选该框)。 - Marcelo Cantos
1
请注意,如果您要表示矩阵,您可能需要使用 numpy - Mike Graham
4
若要快速转置“mat”,请尝试使用“zip(*mat)”。 - PaulMcG
8个回答

16
很多人不知道Python允许使用if来过滤列表推导的结果:
>>> [i for i in range(10) if i % 2 == 0]
[0, 2, 4, 6, 8]

9

我经常使用推导式来构建字典:

my_dict = dict((k, some_func(k)) for k in input_list)

请注意,Python 3 中有字典推导式,因此变成了:

my_dict = {k:some_func(k) for k in input_list}

如果要从元组列表构建类似CSV的数据:

data = "\n".join(",".join(x) for x in input)

虽然不是列表推导式,但仍然很有用:从“切点”列表生成一个范围的列表:

ranges = zip(cuts, cuts[1:])

这些都不是列表推导式,而是生成器表达式。 - Ignacio Vazquez-Abrams
列表推导式只是生成器表达式的一个特殊情况。如果这会让你感到满意,我可以在它们周围加上方括号,但我认为问题提问者对于技术本身更感兴趣,而非细节。 - Nick Johnson

8

要对矩阵mat进行转置:

>>> [list(row) for row in zip(*mat)]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

4
map(list, zip(*mat)) 相对简洁一些(尽管不是列表推导式)。 - Grumdrig
1
简洁并不是一种积极的特质。避免使用map()函数是列表推导式的一个主要优点。 - Glenn Maynard
7
@Glenn 这是一个非常笼统的概括。鉴于推导式是一种更简洁表达循环的方式,如果简洁总是不好的话,那么你应该始终将它们扩展为循环。 - Nick Johnson
也许我应该说“更优雅一些”。:) 我认为map(list,...)更加透明。 - Grumdrig
1
简洁易读是充分利用列表推导式的好处。"简洁"——到了难以理解的地步——这是错误的目标,我认为map(list, zip(*mat))就是这种情况。 - Glenn Maynard
显示剩余2条评论

8

将一个嵌套列表变平:

>>> matrix = [[1,2,3], [4,5,6]]
>>> [x for row in matrix for x in row]
[1, 2, 3, 4, 5, 6]

请记住,对于大型列表,列表推导式版本比sum(matrix, [])更高效。 - David

8
如果“酷”意味着疯狂,我喜欢这个:
def cointoss(n,t):
    return (lambda a:"\n".join(str(i)+":\t"+"*"*a.count(i) for i in range(min(a),max(a)+1)))([sum(randint(0,1) for _ in range(n)) for __ in range(t)])

>>> print cointoss(20,100)
3:    **
4:    ***
5:    **
6:    *****
7:    *******
8:    *********
9:    *********
10:   ********************
11:   *****************
12:   *********
13:   *****
14:   *********
15:   *
16:   **

n和t控制每个测试中抛硬币的次数以及运行测试和绘制分布图的次数。


4

我经常使用这个功能来加载以制表符分隔的文件,其中可能包含以井号开头的注释行:

data = [line.strip().split("\t") for line in open("my_file.tab") \
        if not line.startswith('#')]

当然,它也适用于任何其他注释和分隔符字符。

这太糟糕了。不要把大量的代码压缩到列表推导式中 - 把它拆分开来。 - Glenn Maynard
@Glenn Maynard:同意。列表推导式并不是用来这样使用的,而且这样做会破坏按行迭代文件的目的,因为你只是一次性读取了所有内容,并使用列表推导式对每一行进行处理。不仅如此,文件何时关闭呢?虽然我认识到这种推导式的优点,但也存在缺点,所以请小心使用。这几乎和在C中调用malloc而没有相应的free或在C++中使用new而没有相应的delete一样糟糕。>_< - Dustin
6
我不同意这很糟糕的说法。我认为它很容易阅读,而且比多行版本更易读或同样易读。 - Grumdrig
@Dustin:在Python中(至少在CPython中),当相应的文件对象被解除引用时,文件会被关闭。列表推导式中创建的文件对象在使用时只有一个引用,因此当解释器完成处理时,它将自动被销毁和关闭(请参见https://dev59.com/LXRB5IYBdhLWcg3wl4Sc)。没有必要关闭它。而且有很多情况下你需要一次性获取整个数据集;例如,当你想从概率预测器的输出中制作ROC曲线时。 - Tamás
@Grumdrig 我也同意您的观点,我一直在深入学习Pep8并努力编写更好的Python代码。我始终不同意对大型列表推导式的厌恶。它们比嵌套的for循环快得多。我理解不使用它们的原因,但是如果它们不超过两行,它们是完全可读的。 - Calvin Ellington

3
我目前有几个脚本需要按高度将一组点分成不同的“等级”。假设这些点的z值会松散地聚集在对应于这些等级的某些值周围,并且之间存在较大的空隙。

因此,我有以下函数:

def level_boundaries(zvalues, threshold=10.0):
    '''Finds all elements z of zvalues such that no other element
    w of zvalues satisfies z <= w < z+threshold.'''
    zvals = zvalues[:]
    zvals.sort()
    return [zvals[i] for i, (a, b) in enumerate(pairs(zvals)) if b-a >= threshold]

"pairs" 是直接从 itertools 模块文档中取出的,供参考:

def pairs(iterable):
    'iterable -> (iterable[n], iterable[n+1]) for n=0, 1, 2, ...'
    from itertools import izip, tee
    first, second = tee(iterable)
    second.next()
    return izip(first, second)

一个编造的使用示例(我的实际数据集太大了,无法用作示例):
>>> import random
>>> z_vals = [100 + random.uniform(-1.5,1.5) for n in range(10)]
>>> z_vals += [120 + random.uniform(-1.5,1.5) for n in range(10)]
>>> z_vals += [140 + random.uniform(-1.5,1.5) for n in range(10)]
>>> random.shuffle(z_vals)
>>> z_vals
[141.33225473458657, 121.1713952666894, 119.40476193163271, 121.09926601186737, 119.63057973814858, 100.09095882968982, 99.226542624083109, 98.845285642062763, 120.90864911044898, 118.65196386994897, 98.902094334035326, 121.2741094217216, 101.18463497862281, 138.93502941970601, 120.71184773326806, 139.15404600347946, 139.56377827641663, 119.28279815624718, 99.338144106822554, 139.05438770927282, 138.95405784704622, 119.54614935118973, 139.9354467277665, 139.47260445000273, 100.02478729763811, 101.34605205591622, 138.97315450408186, 99.186025111246295, 140.53885845445572, 99.893009827114568]
>>> level_boundaries(z_vals)
[101.34605205591622, 121.2741094217216]

你能展示一下zvalues的可能导入方式吗? - christangrant
我已经添加了一个示例,如果这是你的意思。 - Peter Milley

2
只要你想使用Python中受函数式编程启发的部分,就可以考虑map、filter、reduce和zip——它们都是Python中提供的。

1
Guido解释了为什么列表推导通常比map/filter更有意义:http://www.artima.com/weblogs/viewpost.jsp?thread=98196 - Tony Arkles

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