推导式是一个可以从现有数据序列构建另一个新的数据序列的结构体。Python共提供了三种推导式,在Python 2和 Python 3中都支持:
- 列表(list)推导式
- 字典(dict)推导式
- 集合(set)推导式
其中,最常用的是列表推导(list comprehension,又称列表解析式)。在这篇文章中,将用列表推导作为例子对Python的推导式进行系统性的介绍,然后再简单介绍字典推导与集合推导,之后还会介绍推导式与Python内置函数集成的例子,最后,会给出一些比较实用的推导式,用以巩固练习。
1 列表推导的含义与语法
列表推导式提供了一种简明扼要的方法来创建列表。列表推导的语法是在一个中括号里包含一个表达式,然后是一个for语句,然后是0个或多个for语句或者if语句。
列表推导中的“表达式”可以是任意的,也就是说,可以在创建新的列表时,顺便进行一些运算操作。运算操作的结果,将会构成新的列表进行返回。
列表推导的形式化定义如下:
variable = [expr for iter_item in iterable if cond_expr]
列表推导对快速生成列表非常有用,尤其适合遍历一个列表,并进行简单的计算,然后产生一个新的列表。例如,要获取0~9的平方的列表,初级Python工程师通常会这样做:
squared = []
for x in range(10):
squared.append(x**2)
高级的Python工程师一般会通过列表推导来简化代码,并且可读性更强,如下所示:
squared = [x**2 for x in range(10)]
上面这个例子已经演示了列表推导的定义和使用。读者需要注意的是,在列表推导中,表达式并不是必须的,如下所示:
>>> [ i for i in range(5) ]
[0, 1, 2, 3, 4]
列表推导还支持if语句:
>>> [ i*i for i in range(5) if i % 2 == 0 ]
[0, 4, 16]
列表推导也可以带任意数量的嵌套for循环,并且每一个for循环后面都有可选的if语句,形式化定义如下:
[ expression for x in X [if condition]
for y in Y [if condition]
...
for n in N [if condition] ]
下面是在列表推导中使用嵌套for循环的例子:
>>> [ i * j for i in "abc" for j in range(1, 4) ]
['a', 'aa', 'aaa', 'b', 'bb', 'bbb', 'c', 'cc', 'ccc']
>>> [ i * j for i in "abc" for j in range(1, 4) if j % 2 == 0 ]
['aa', 'bb', 'cc']
虽然列表推导可以简洁明了的做一些计算,但是,嵌套for循环除了增加难以理解的程度,并没有带来实际的好处。所以,笔者并不推荐大家在列表推导中使用嵌套的for循环。
在列表推导的“表达式”里面,可以进行简单的计算,也可以直接调用函数进行一些运算操作,如下所示:
>>> [ s.strip() for s in ("a ", " b", " c ") ]
['a', 'b', 'c']
但是,在列表推导中使用lambda时需要格外小心,并不能直接在列表推导中使用lambda语句:
>>> [ lambda s: s.strip() for s in ("a ", " b", " c ") ]
[<function __main__.<lambda>>,
<function __main__.<lambda>>,
<function __main__.<lambda>>]
如果要在列表推导中使用lambda,可以先定义lambda,并将lambda保存到变量中,然后在列表推导中进行调用,如下所示:
>>> f = lambda s: s.strip()
>>> [ f(s) for s in ("a ", " b", " c ") ]
['a', 'b', 'c']
2 集合推导与字典推导
Python中的列表推导使用最为广泛,除了列表推导之外,其实还有集合推导与字典推导。只要将列表推导表达式中的中括号替换成大括号,产生的结果将会是一个集合。集合与列表是完全不同的两种存在,对于集合来说,会自动去除集合中的重复元素,如下所示:
>>> { i * j for i in range(3) for j in range(2) }
set([0, 1, 2])
将集合推导中,生成结果的部分修改为字典的key: val
形式,就会产生一个字典,如下所示:
>>> { i : i*i for i in range(5) }
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> { i : i*i for i in range(5) if i % 2 == 0}
{0: 0, 2: 4, 4: 16}
字典推导的一个重要用处,就是对字典进行过滤,例如,下面的字典是一份股票数据:
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75}
如果只想查看股票价格大于100美元的股票,则需要通过字典的val来进行过滤,这在字典推导中非常方便:
>>> { key: val for key, val in prices.items() if val > 100 }
{'AAPL': 612.78, 'IBM': 205.55}
当然,有了字典推导以后,也可以方便地按照key进行过滤,如下所示:
>>> { key: prices[key] for key in prices.keys() if key not in ['FB', 'IBM'] }
{'HPQ': 37.2, 'AAPL': 612.78, 'ACME': 45.23}
3 列表与Python内置函数集成
列表推导式提供了一种简明扼要的方法来创建列表,并且可以进行一些过滤和计算,然后产生一个新的列表。Python中许多内置函数,都设计得足够的通用:接受的参数是一个“可迭代”对象。
列表显然是一个可迭代的对象,因此,将列表推导与Python的内置函数相结合,就可以产生一些意想不到的效果。
例如,在MySQL中,通过show master logs
命令可以查看所有的binlog文件和大小,下面这个函数是用Python代码获取所有binlog文件所占的大小。
def get_binlog_size(self):
"""
mysql> show master logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000003 | 622858092 |
| mysql-bin.000004 | 1408 |
+------------------+-----------+
2 rows in set (0.00 sec)
rows = (('mysql-bin.000003', 622858092), ('mysql-bin.000004', 1408))
"""
sql = "show master logs"
rows = self.exec_sql(sql)
# return sum([long(row[1]) for row in rows])
return sum(long(row[1]) for row in rows)
在这个实现中,可以将列表推导两边的中括号省略,就变成了一个生成器,但是主要的思想和这篇文章中介绍的“列表推导”相同。
下面是一个与bool函数相结合的例子,用以判断MySQL的插件是否已经存在:
def is_plugin_exists(self, plugin_name):
"""
show plugins;
('rpl_semi_sync_master', 'ACTIVE', 'REPLICATION', 'semisync_master.so', 'GPL'),
('rpl_semi_sync_slave', 'ACTIVE', 'REPLICATION', 'semisync_slave.so', 'GPL'))
"""
sql = "show plugins"
rows = self.exec_sql(sql)
return bool([row for row in rows if row[0] == plugin_name])
在本系列文章中的第一篇中,还介绍了一个与any函数相结合的列子。
4 列表推导、集合推导和字典推导实战
为了便于读者理解,下面再提供一些实战的案例。读者也可以尝试自己通过列表推导、集合推导和字典推导来解决问题,然后再查看笔者提供的答案。
- 0~40 所有偶数的平方
- 1~20所有奇数的平方的集合
- 30~40所有奇数与它的平方的字典
- 当前用户home目录下所有的文件列表
- 当前用户home目录下所有的目录列表
- 当前用户home目录下所有目录的目录名到绝对路径之间的字典
- 当前用户home目录下所有文件到文件大小之间的字典
上面7个习题的答案如下:
[ i*ifor iin range(30, 41) if i% 2 == 0 ]
{ i*i for i in range(1, 21) if i % 2 != 0 }
{ i:i*i for iin range(30, 40) if i% 2 != 0 }
[ item for item in os.listdir(os.path.expanduser('~')) if os.path.isfile(item) ]
[ item for item in os.listdir(os.path.expanduser('~')) if os.path.isdir(item) ]
{ item: os.path.realpath(item)for item in os.listdir(os.path.expanduser('~')) if os.path.isdir(item) }
{ item: os.path.getsize(item)for item in os.listdir(os.path.expanduser('~')) if os.path.isfile(item) }
最后再来一个稍有难度的习题,将一句话中,所有的单词首字母变成大写。例如,将"Remember, remember, the fifth of November.“转换为"Remember, Remember, The Fifth Of November.”。
这个任务在word中是没办法实现的,在其他编程语言中实现起来也比较困难,但是,在Python语言中只需要一行代码即可。如下所示:
" ".join([ s.capitalize() for s in 'Remember, remember, the fifth of November.'.split()])
5 总结
在这篇文章中,我们系统性地介绍了列表推导的语法和使用,与此同时,还介绍了集合推导与字典推导这两个容易被忽略的表达式,并且介绍了如何将推导式与Python的内置函数相结合,产生更加意想不到的想过。最后,通过实际案例来巩固推导式的知识。
作者介绍
赖明星,架构师、作家。现就职于腾讯,参与并主导下一代金融级数据库平台研发。有多年的 Python 开发经验和一线互联网实战经验,擅长 C、Python、Java、MySQL、Linux 等主流技术。国内知名的 Python 技术专家和 Python 技术的积极推广者,著有《Python Linux 系统管理与自动化运维》一书。