活动地址:CSDN21天学习挑战赛
在Python中,序列类型的数据结构有很多,但是不管是其中的哪种数据结构,字符串、列表、字节序列、数组、XML元素,亦或是数据库查询结果,它们都公用一套丰富的操作:迭代、切片、排序,还有拼接。
本篇笔记先简略介绍一下Python内置数据类型中关于列表和元组的相关知识。
以下关于列表推导式的介绍可能有些冗长,但是说清了笔者自己的理解
。
内置序列类型概览
Python 标准库用 C 实现了丰富的序列类型,列举如下。
容器序列
list
、tuple
和collections.deque
这些序列能存放不同类型的数据。扁平序列
str
、bytes
、bytearray
、memoryview
和array.array
,这类序列只能容纳一种类型
容器序列中存的是数据的引用,而扁平序列中存的是数据的值。
序列按照是否可以修改
来分类,如下:
可变序列:
*
list
、bytearray
、array.array
、collections.deque
和memoryview
。不可变序列:
tuple
、str
和bytes
。
下面的UML图展示了可变序列类型和不可变序列类型的差别和联系。
通过记住
这些类的共有特性,可以更好地理解它们。
需要注意的是,Python内置的序列类型并不是直接从Sequence
和MutableSequence
这两个抽象基类(Abstract Base Class,ABC)直接继承过来的。了解这些基类能够让我们总结出完整的序列类型包含哪些功能。
列表推导和生成器表达式
列表推导
列表推导式最主要的功能就是以简洁的代码构建一个新的列表。
-
示例:列表推导式很多书上并没有给出具体语法,大多都是例子。但是笔者自己总结了下语法,后面会有介绍。
-
symbols = 'abcABC' ascii = [ord(symbol) for symbol in symbols] print(ascii) #结果为: #[97, 98, 99, 65, 66, 67]
-
这里的ord函数接收一个字符(长度为1的字符串),将其转换为对应的ASCII码值。除了上面那种写法,列表推导式可以使用
if
语句进行过滤。 -
symbols = 'abcABC' ascii = [ord(symbol) for symbol in symbols if ord(symbol) < 97] print(ascii) # 结果为: #[65, 66, 67]
-
-
语法:
-
从上面的例子可以感受到,列表推导式是由
[]
、表达式
(这里就是上面的ord(symbol)
部分)、循环和条件判断三个部分组成。这里抽象为下面这种表示形式。[表达式 循环和条件判断]
表达式是将要生成的列表中元素的统一表达形式。循环和条件判断分别是生成迭代变量和过滤结果的。循环和条件判断分别由
for
语句和if
语句实现。在循环和条件判断
部分for
语句必须是第一个出现的,即列表推导式应是以下形式[表达式 for ... ...]
。 -
进一步理解:
对于上面的一段代码:
ascii = [ord(symbol) for symbol in symbols if ord(symbol) < 97]
我们可以将其改写为如下形式:
ascii = [] for symbol in symbols: if ord(symbol) < 97: ascii.append(ord(symbol))
对上面这段改写的代码进行抽象就是:
result = [] for: if: result.append(表达式)
-
一个更为复杂的示例:
result = [(x, y) for x in range(6) if x != 3 for y in range(7) if y != 5 ]
显然,根据先前的展示,现在这个复杂的示例可以改写为:
result = [] for x in range(6): if x != 3: for y in range(7): if y != 5: result.append((x, y))
对其进行抽象:
result = [] for: if: for: if: result.append(表达式)
相信看了上面的介绍,即使你是Python小白,你也应该能够很清楚如何去编写自己的列表推导式了。
-
-
计算笛卡尔积:书中给出了一个示例,这里可以当做习题。
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
tshirts = [(color, size) for color in colors for size in sizes]
print(tshirts)
这段代码仍然可以使用上面的改写方式进行改写以进一步理解列表生成逻辑。对于有多重循环的列表推导式,需要重点注意元素的生成顺序。改写后就很容易理解了。
生成器表达式
相比于列表推导式,生成器表达式更让人喜爱的原因就是其遵守迭代器协议,简单来说,它能够逐个产生元素,极大地节省内存空间。而列表推导式是直接生成整个列表。
有关生成器的概念此处不做介绍。
生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而已。同样的生成器的元素生成逻辑也可以按照列表推导式的方式来分析。
- 示例:
symbols = 'abcABC'
result = (ord(symbol) for symbol in symbols)
print(result)
print(type(result))
#结果如下:
#<generator object func4.<locals>.<genexpr> at 0x0000022D050E9BA0>
#<class 'generator'>
result = tuple(ord(symbol) for symbol in symbols)
print(result)
#结果如下:
#(97, 98, 99, 65, 66, 67)
这里有一个小技巧
可以注意一下,当一个函数调用时,需要传递的参数只有一个,且该参数为一个生成器表达式时,那么生成器表达式的()
可以省略,如上面代码中的tuple(ord(symbol) for symbol in symbols)
就省略了。
再看一个不能省略的例子:
>>> import array
>>> array.array('I', (ord(symbol) for symbol in symbols))
array('I', [36, 162, 163, 165, 8364, 164])
参考资料
- 流畅的Python