《手把手陪您学Python》25——列表推导式 - Go语言中文社区

《手把手陪您学Python》25——列表推导式


经过前几篇文章的学习,我们基本上掌握了Python中最为基础的几种数据类型,包括列表、元组、字典和集合,大家可以通过点击下方链接进行学习。

《手把手陪您学Python》20——列表

《手把手陪您学Python》21——元组

《手把手陪您学Python》22——字典

《手把手陪您学Python》24——集合

今天,我们将要学习的是一种叫做推导式的方法,它允许我们过滤一个容器的元素,用一种简明的表达式,转换传递给过滤器的元素,从而生成一个新的序列。具体又包括列表推导式、集合推导式和字典推导式。

1、列表推导式

以列表推导式为例,以前我们定义一个列表时,列表里有什么元素就要写什么元素,如果元素的数量比较多时,输入量还是比较大的,而且不便于阅读。最多可以使用range()函数减少输入量,但毕竟这种情况还是少数。

列表推导式就是用来解决列表元素满足某一特定条件时,列表的定义或者说列表元素的输入问题的。

使用列表推导式定义列表时,不需要像我们之前定义列表一样,将列表的元素一一填写在[]中,而是在[]中写上要满足的某一条件的表达式,最终生成满足我们需求的列表。这个过程就是上面定义中所说的“过滤”的概念。

列表推导式的基本形式可以写为:

list_comp = [expr for val in collection if condition]

从这种列表推导式的形式,我们可以看出来,它很像我们使用for循环生成列表的过程,for循环中的条件就是我们在列表推导式中写的条件,所以上面列表推导式的基本形式,也可以用下面的for循环来表达,它们是等价的:

result = []
for val in collection:
    if condition:
        result.append(expr)

下面让我们通过实例来看一下这两种表达式的效果为什么是等效的,而且有了实际的例子,对列表推导式的理解也会更容易。

这个实例的目标是对给定的一个字符串列表,将其长度大于2的元素,全部改为大写,并输出满足这个条件的列表。

In [1]: lst = ["Tom", "amy", "h", "James", "we", "Python", "3", (1, 2)]
​
In [2]: [val.upper() for val in lst if len(val) > 2]   # 使用列表推导式的方法
Out[2]: ['TOM', 'AMY', 'JAMES', 'PYTHON']
​
In [3]: result = []   # 使用for循环的方法
        for val in lst:
            if len(val) > 2:
                result.append(val.upper())
        result
Out[3]: ['TOM', 'AMY', 'JAMES', 'PYTHON']

从以上实例中可以看到,虽然两种方法得到的结果完全一致,但是代码量却差别明显。这就是推导式的优势所在,因此也受到很多Python程序员的喜爱。

2、集合推导式

集合推导式和列表推导式非常类似,只是将列表的[]换成了集合的{}。

set_comp = {expr for val in collection if condition}

所以,可以使用与列表推导式相同的实例进行演示。

In [4]: {val.upper() for val in lst if len(val) > 2}   # 使用集合推导式
Out[4]: {'AMY', 'JAMES', 'PYTHON', 'TOM'}

在使用集合推导式的时候,要注意两点,一是可能会出现重复的情况,这时集合的输出结果就会“去重”;二是集合是无序的,其输出结果的“顺序”与列表的不同。

3、字典推导式

字典因为是一个二元结构,因此字典推导式会稍微有点复杂,但万变不离其宗,原理和列表推导式是一样的,其基本形式为:

dict_comp = {key_expr : value_expr for val in collection if condition}

如果我们要将a列表中所有大于3的元素,都将元素作为键,将元素的平方作为值,可以写出这样的字典表达式:

In [5]: a = [1, 2, 3, 4, 5, 6, 7]
        {val : val**2 for val in a if val > 3}
Out[5]: {4: 16, 5: 25, 6: 36, 7: 49}

因为字典的键是不可重复的,如果key_expr是固定的,则只能生成含有一个元素的字典,例如:

In [6]: a = [1, 2, 3, 4, 5, 6, 7]
        {1 : val**2 for val in a if val > 3}   # 实际上就是后面的值将前面的值覆盖了
Out[6]: {1: 49}

对于需要同时使用索引值和元素值的情况,我们可以利用在《手把手陪您学Python》23——内置序列函数一文中讲到的内置序列函数emunerate()来实现。

In [7]: a = [1, 2, 3, 4, 5, 6, 7]
​
In [8]: {val1 : val2**2 for (val1, val2) in enumerate(a) if val2 > 3}
Out[8]: {3: 16, 4: 25, 5: 36, 6: 49}
​
In [9]: {val1 : val2**2 for (val1, val2) in enumerate(a) if val1 > 3}   # 在这个实例中,改变了一个变量,得到了完全不一样的结果,大家看一下这个表达式的含义又是什么
Out[9]: {4: 25, 5: 36, 6: 49}

4、简化推导式

之前我们探讨的都是完整的推导式的情况,对于一些没有条件(condition)要求的情况,我们可以将推导式中的“if condition”部分省略,形成简化的推导式。

还是以列表推导式为例,这次我们需要输出的是给定列表中元素长度的列表。

In [10]: lst = ["Tom", "amy", "h", "James", "we", "Python", "3", (1, 2)]
         [len(val) for val in lst]
Out[10]: [3, 3, 1, 5, 2, 6, 1, 2]

5、map()函数

另外一种简化的方法是使用map()函数。map()也是Python的内置函数,它的作用是对参数中的序列,进行指定函数的计算。也就是说函数以及序列都是map()参数,其中函数的名称必须放在第一个参数上,序列的数量由函数决定,函数的参数需要几个,map中的序列就有几个。

map(function, iterable, ...)

使用map()函数,上面的代码可以更加简化。因为len()函数只有一个参数,因此序列也只有一个。

In [11]: lst = ["Tom", "amy", "h", "James", "we", "Python", "3", (1, 2)]
         list(map(len, lst))
Out[11]: [3, 3, 1, 5, 2, 6, 1, 2]

让我们再来看一个有多个序列作为参数的情况,比如比较三个序列对应位置的大小,并返回最大值。

In [12]: list(map(max,[8,2,3],(3,4,5,6),{6:'a', 9:'b', 2:'c'}))
Out[12]: [8, 9, 5]

6、嵌套列表推导式

最后再用一个比较复杂的内容结束我们今天对列表推导式的介绍。在看这部分内容之前,请大家确认已经对之前列表推导式完全理解了,否则代码看起来可能会有点晕。

嵌套列表推导式,就是在列表推导式里的表达式(expr)中,再嵌入一个列表推导式,形成列表推导式的嵌入。

为了方便大家理解和比较,还继续使用我们之前的例子,lst = ["Tom", "amy", "h", "James", "we", "Python", "3", (1, 2)]。在这个例子中,我们使用列表推导式,过滤出了长度大于2的元素,并将其全部转化成大写字母进行了输出。

但是,如果我们初始的列表是一个嵌套列表,是否还可以用一样的列表推导式呢,我们来试一下。

In [13]: lst1 = [["Tom", "amy", "h", "James"], ["we", "Python", "3", (1, 2)]]   # 嵌套列表
         [val.upper() for val in lst1 if len(val) > 2]   # 使用和之前例子一样的列表推导式
Out[13]: ---------------------------------------------------------------------------
          AttributeError                            Traceback (most recent call last)
          <ipython-input-15-0dc770476ed2> in <module>
                1 lst1 = [["Tom", "amy", "h", "James"], ["we", "Python", "3", (1, 2)]]   # 嵌套列表
          ----> 2 [val.upper() for val in lst1 if len(val) > 2]   # 使用和之前例子一样的列表推导式
          <ipython-input-15-0dc770476ed2> in <listcomp>(.0)
                1 lst1 = [["Tom", "amy", "h", "James"], ["we", "Python", "3", (1, 2)]]   # 嵌套列表
          ----> 2 [val.upper() for val in lst1 if len(val) > 2]   # 使用和之前例子一样的列表推导式
               
          AttributeError: 'list' object has no attribute 'upper'

大家可以看到,当我们的初始列表变成了嵌套列表,如果还使用原来的列表推导式,就会报错,而报错的原因是list不能upper。

让我们分析一下原因。

对于原来的列表(lst),我们是对其中的元素(一个个字符串或元组)进行长度的过滤,最终的结果是对长度大于2的元素(字符串)转化成大写字母。

而对于新的列表(lst1),我们是对其中的元素(嵌套其中的两个列表)进行长度的过滤,两个列表的长度都大于2,到这里都没有问题。问题在与这两个列表经过过滤后,再进行.upper()的操作时,因为列表不能进行.upper()方法,所以报错了。

那么如何进行修改呢?这时就需要用到我们的嵌套列表推导式了。嵌套列表推导式也可以理解为对经过列表推导式的结果再进行一次列表推导式,我们也可以先把这个过程用语言拆解一下。

第一步,先用列表推导式把新的列表(lst1)进行过滤,即使不过滤,也要把这一步的条件写出来——for lsts in lst1。这个代码大家可以理解为for循环的遍历过程,就是将lst1中的所有列表进行遍历。如果没有条件限制,就是lst1中的所有元素全都保留;如果要进行过滤,比如长度小于3的列表才保留,就可以加上判断条件,写为——for lsts in lst1 if len(lsts) < 3。

第二步,对过滤后的列表lsts再进行列表推导式的推导,这时就和我们最初的写法一样了——val.upper() for val in lsts if len(val) > 2。这一步和我们之前写的代码是完全一样的,只不过过滤的初始列表从lst1变成了lsts,也就是经过我们上一步过滤后的列表。

第三步,将前两部的代码合并,将第一步的代码放入第二步代码的for前面即可,即下面标红的部分。请注意标红的部分的变量lsts是第一步过滤的过程并得到结果,和第二步的初始过滤列表是同一个,即下面标蓝的部分,所以这两部分的变量名称要保持一致。

val.upper() for lsts in lst1 for val in lsts if len(val) > 2

In [14]: lst1 = [["Tom", "amy", "h", "James"], ["we", "Python", "3", (1, 2)]]   # 嵌套列表
         [val.upper() for lsts in lst1 for val in lsts if len(val) > 2]
Out[14]: ['TOM', 'AMY', 'JAMES', 'PYTHON']

大家的疑问可能是为什么要将第一次过滤的代码放在第二次过滤代码的for的前面。其实就像是for循环的嵌套一样,先for lsts in lst1得到一个结果,再对这个结果进行for val in lsts的遍历。大家可以参考下面这个不太规范的for循环过程,主要是为了让大家更好地了解嵌套推导的过程,以及不同层级推导过程的位置。

for lsts in lst1:
    for val in lsts:
        if len(val) > 2:
            val.upper()

以上实例是不对内层列表进行过滤的情况,比较简单。如果内层列表也需要过滤的话,会更加复杂一点,但万变不离其宗,就是在第一个嵌套的列表推导式中加入condition即可。

在下面的例子中,我们给初始列表增加了一个长度为2的列表元素,通过先将长度小于3的列表过滤出来,再进行转换成大写的操作。此时,就是在第一个列表推导式中增加了if len(lsts) < 3的条件。

val.upper() for lsts in lst2 if len(lsts) < 3 for val in lsts if len(val) > 2

In [15]: lst2 = [["Tom", "amy", "h", "James"], ["we", "Python", "3", (1, 2)], ["you", "Good"]]   # 嵌套列表
         [val.upper() for lsts in lst2 if len(lsts) < 3 for val in lsts if len(val) > 2]
Out[15]: ['YOU', 'GOOD']

这个过程同样可以用类似不太规范的for循环过程来表达,请大家参考:

for lsts in lst1:
    if len(lsts) < 3:
        for val in lsts:
            if len(val) > 2:
                val.upper()

按照这个规律,嵌套列表推导式理论上可以无限地嵌套下去。但并不建议使用超过两层的嵌套推导式,因为推导式本身的目的是为了代码的可读性,如果嵌套的层级过多,就可能会适得其反了。

以上就是对列表推导式、集合推导式和字典推导式的讲解。希望大家能够在理解的基础上灵活运用,提高我们代码的可读性。

从下一篇开始,我们将用几篇文章,重点介绍函数的相关内容。这里所说的函数不仅包括Python中内置的函数,还有最最最重要的自定义函数的相关内容,敬请关注。

 

 


感谢阅读本文!如有任何问题,欢迎留言,一起交流讨论^_^

要阅读《手把手陪您学Python》系列文章的其他篇目,请关注公众号点击菜单选择,或点击下方链接直达。

《手把手陪您学Python》1——为什么要学Python?

《手把手陪您学Python》2——Python的安装

《手把手陪您学Python》3——PyCharm的安装和配置

《手把手陪您学Python》4——Hello World!

《手把手陪您学Python》5——Jupyter Notebook

《手把手陪您学Python》6——字符串的标识

《手把手陪您学Python》7——字符串的索引

《手把手陪您学Python》8——字符串的切片

《手把手陪您学Python》9——字符串的运算

《手把手陪您学Python》10——字符串的函数

《手把手陪您学Python》11——字符串的格式化输出

《手把手陪您学Python》12——数字

《手把手陪您学Python》13——运算

《手把手陪您学Python》14——交互式输入

《手把手陪您学Python》15——判断语句if

《手把手陪您学Python》16——循环语句while

《手把手陪您学Python》17——循环的终止

《手把手陪您学Python》18——循环语句for

《手把手陪您学Python》19——第一阶段小结

《手把手陪您学Python》20——列表

《手把手陪您学Python》21——元组

《手把手陪您学Python》22——字典

《手把手陪您学Python》23——内置序列函数

《手把手陪您学Python》24——集合

For Fans:关注“亦说Python”公众号,回复“手25”,即可免费下载本篇文章所用示例语句。

亦说Python——Python爱好者的学习分享园地
 
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mnpy2019/article/details/102802096
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-02-13 14:10:31
  • 阅读 ( 1235 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢