Python 数据分析(PYDA)第三版(一)(2)https://developer.aliyun.com/article/1482369
三、内置数据结构、函数和文件
原文:
wesmckinney.com/book/python-builtin
译者:飞龙
此开放访问网络版本的《Python 数据分析第三版》现已作为印刷版和数字版的伴侣提供。如果您发现任何勘误,请在此处报告。请注意,由 Quarto 生成的本站点的某些方面与 O’Reilly 的印刷版和电子书版本的格式不同。
如果您发现本书的在线版本有用,请考虑订购纸质版或无 DRM 的电子书以支持作者。本网站的内容不得复制或再生产。代码示例采用 MIT 许可,可在 GitHub 或 Gitee 上找到。
本章讨论了内置到 Python 语言中的功能,这些功能将在整本书中被广泛使用。虽然像 pandas 和 NumPy 这样的附加库为更大的数据集添加了高级计算功能,但它们旨在与 Python 的内置数据操作工具一起使用。
我们将从 Python 的主要数据结构开始:元组、列表、字典和集合。然后,我们将讨论如何创建自己可重用的 Python 函数。最后,我们将看看 Python 文件对象的机制以及如何与本地硬盘交互。
3.1 数据结构和序列
Python 的数据结构简单而强大。掌握它们的使用是成为熟练的 Python 程序员的关键部分。我们从元组、列表和字典开始,它们是一些最常用的序列类型。
元组
元组是 Python 对象的固定长度、不可变序列,一旦分配,就无法更改。创建元组的最简单方法是使用括号括起的逗号分隔的值序列:
In [2]: tup = (4, 5, 6) In [3]: tup Out[3]: (4, 5, 6)
在许多情况下,括号可以省略,所以这里我们也可以这样写:
In [4]: tup = 4, 5, 6 In [5]: tup Out[5]: (4, 5, 6)
您可以通过调用tuple
将任何序列或迭代器转换为元组:
In [6]: tuple([4, 0, 2]) Out[6]: (4, 0, 2) In [7]: tup = tuple('string') In [8]: tup Out[8]: ('s', 't', 'r', 'i', 'n', 'g')
元素可以使用方括号[]
访问,就像大多数其他序列类型一样。与 C、C++、Java 和许多其他语言一样,在 Python 中,序列是从 0 开始索引的:
In [9]: tup[0] Out[9]: 's'
当您在更复杂的表达式中定义元组时,通常需要将值括在括号中,就像在创建元组的示例中一样:
In [10]: nested_tup = (4, 5, 6), (7, 8) In [11]: nested_tup Out[11]: ((4, 5, 6), (7, 8)) In [12]: nested_tup[0] Out[12]: (4, 5, 6) In [13]: nested_tup[1] Out[13]: (7, 8)
虽然存储在元组中的对象本身可能是可变的,但一旦创建了元组,就无法修改存储在每个槽中的对象:
In [14]: tup = tuple(['foo', [1, 2], True]) In [15]: tup[2] = False --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-15-b89d0c4ae599> in <module> ----> 1 tup[2] = False TypeError: 'tuple' object does not support item assignment
如果元组中的对象是可变的,比如列表,您可以就地修改它:
In [16]: tup[1].append(3) In [17]: tup Out[17]: ('foo', [1, 2, 3], True)
您可以使用+
运算符连接元组以生成更长的元组:
In [18]: (4, None, 'foo') + (6, 0) + ('bar',) Out[18]: (4, None, 'foo', 6, 0, 'bar')
将元组乘以一个整数,与列表一样,会产生该元组的多个副本的效果:
In [19]: ('foo', 'bar') * 4 Out[19]: ('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')
请注意,对象本身并没有被复制,只有对它们的引用。
解包元组
如果您尝试对类似元组的变量表达式进行赋值,Python 将尝试在等号右侧解包值:
In [20]: tup = (4, 5, 6) In [21]: a, b, c = tup In [22]: b Out[22]: 5
即使包含嵌套元组的序列也可以解包:
In [23]: tup = 4, 5, (6, 7) In [24]: a, b, (c, d) = tup In [25]: d Out[25]: 7
使用这个功能,您可以轻松交换变量名,这在许多语言中可能看起来像:
tmp = a a = b b = tmp
但是,在 Python 中,交换可以这样做:
In [26]: a, b = 1, 2 In [27]: a Out[27]: 1 In [28]: b Out[28]: 2 In [29]: b, a = a, b In [30]: a Out[30]: 2 In [31]: b Out[31]: 1
变量解包的常见用途是迭代元组或列表的序列:
In [32]: seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)] In [33]: for a, b, c in seq: ....: print(f'a={a}, b={b}, c={c}') a=1, b=2, c=3 a=4, b=5, c=6 a=7, b=8, c=9
另一个常见用途是从函数返回多个值。我稍后会更详细地介绍这个问题。
有一些情况下,您可能希望从元组的开头“摘取”一些元素。有一种特殊的语法可以做到这一点,*rest
,这也用于函数签名中捕获任意长的位置参数:
In [34]: values = 1, 2, 3, 4, 5 In [35]: a, b, *rest = values In [36]: a Out[36]: 1 In [37]: b Out[37]: 2 In [38]: rest Out[38]: [3, 4, 5]
这个rest
位有时是您想要丢弃的内容;rest
名称没有特殊之处。作为一种惯例,许多 Python 程序员会使用下划线(_
)表示不需要的变量:
In [39]: a, b, *_ = values
元组方法
由于元组的大小和内容不能被修改,因此实例方法非常少。一个特别有用的方法(也适用于列表)是count
,它计算值的出现次数:
In [40]: a = (1, 2, 2, 2, 3, 4, 2) In [41]: a.count(2) Out[41]: 4
列表
与元组相反,列表是可变长度的,其内容可以就地修改。列表是可变的。您可以使用方括号[]
定义它们,也可以使用list
类型函数:
In [42]: a_list = [2, 3, 7, None] In [43]: tup = ("foo", "bar", "baz") In [44]: b_list = list(tup) In [45]: b_list Out[45]: ['foo', 'bar', 'baz'] In [46]: b_list[1] = "peekaboo" In [47]: b_list Out[47]: ['foo', 'peekaboo', 'baz']
列表和元组在语义上是相似的(尽管元组不能被修改),并且可以在许多函数中互换使用。
list
内置函数在数据处理中经常用作实例化迭代器或生成器表达式的方法:
In [48]: gen = range(10) In [49]: gen Out[49]: range(0, 10) In [50]: list(gen) Out[50]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
添加和删除元素
元素可以使用append
方法附加到列表的末尾:
In [51]: b_list.append("dwarf") In [52]: b_list Out[52]: ['foo', 'peekaboo', 'baz', 'dwarf']
使用insert
可以在列表中的特定位置插入元素:
In [53]: b_list.insert(1, "red") In [54]: b_list Out[54]: ['foo', 'red', 'peekaboo', 'baz', 'dwarf']
插入索引必须在列表的长度之间,包括 0 和长度。
警告:
与append
相比,insert
的计算成本较高,因为必须在内部移动后续元素的引用以为新元素腾出空间。如果需要在序列的开头和结尾插入元素,您可能希望探索collections.deque
,这是一个双端队列,专为此目的进行了优化,并且包含在 Python 标准库中。
insert
的反向操作是pop
,它会删除并返回特定索引处的元素:
In [55]: b_list.pop(2) Out[55]: 'peekaboo' In [56]: b_list Out[56]: ['foo', 'red', 'baz', 'dwarf']
可以使用remove
按值删除元素,它会定位第一个这样的值并将其从列表中删除:
In [57]: b_list.append("foo") In [58]: b_list Out[58]: ['foo', 'red', 'baz', 'dwarf', 'foo'] In [59]: b_list.remove("foo") In [60]: b_list Out[60]: ['red', 'baz', 'dwarf', 'foo']
如果不关心性能,通过使用append
和remove
,可以使用 Python 列表作为类似集合的数据结构(尽管 Python 有实际的集合对象,稍后讨论)。
使用in
关键字检查列表是否包含一个值:
In [61]: "dwarf" in b_list Out[61]: True
关键字not
可以用来否定in
:
In [62]: "dwarf" not in b_list Out[62]: False
检查列表是否包含一个值比使用字典和集合慢得多(即将介绍),因为 Python 会在线性扫描列表的值,而可以在常量时间内检查其他值(基于哈希表)。
连接和组合列表
与元组类似,使用+
将两个列表相加会将它们连接起来:
In [63]: [4, None, "foo"] + [7, 8, (2, 3)] Out[63]: [4, None, 'foo', 7, 8, (2, 3)]
如果已经定义了一个列表,可以使用extend
方法将多个元素附加到其中:
In [64]: x = [4, None, "foo"] In [65]: x.extend([7, 8, (2, 3)]) In [66]: x Out[66]: [4, None, 'foo', 7, 8, (2, 3)]
请注意,通过加法进行列表连接是一种相对昂贵的操作,因为必须创建一个新列表并复制对象。通常最好使用extend
将元素附加到现有列表中,特别是如果您正在构建一个大列表。因此:
everything = [] for chunk in list_of_lists: everything.extend(chunk)
比连接替代方案更快:
everything = [] for chunk in list_of_lists: everything = everything + chunk
排序
您可以通过调用其sort
函数就地对列表进行排序(而不创建新对象):
In [67]: a = [7, 2, 5, 1, 3] In [68]: a.sort() In [69]: a Out[69]: [1, 2, 3, 5, 7]
sort
有一些选项,偶尔会派上用场。其中之一是能够传递一个次要排序键——即生成用于对对象进行排序的值的函数。例如,我们可以按字符串的长度对字符串集合进行排序:
In [70]: b = ["saw", "small", "He", "foxes", "six"] In [71]: b.sort(key=len) In [72]: b Out[72]: ['He', 'saw', 'six', 'small', 'foxes']
很快,我们将看一下sorted
函数,它可以生成一份排序后的一般序列的副本。
切片
您可以使用切片表示法选择大多数序列类型的部分,其基本形式是将start:stop
传递给索引运算符[]
:
In [73]: seq = [7, 2, 3, 7, 5, 6, 0, 1] In [74]: seq[1:5] Out[74]: [2, 3, 7, 5]
切片也可以用序列赋值:
In [75]: seq[3:5] = [6, 3] In [76]: seq Out[76]: [7, 2, 3, 6, 3, 6, 0, 1]
虽然start
索引处的元素被包括在内,但stop
索引不包括在内,因此结果中的元素数量为stop - start
。
start
或stop
可以省略,此时它们分别默认为序列的开头和序列的结尾:
In [77]: seq[:5] Out[77]: [7, 2, 3, 6, 3] In [78]: seq[3:] Out[78]: [6, 3, 6, 0, 1]
负索引相对于末尾切片序列:
In [79]: seq[-4:] Out[79]: [3, 6, 0, 1] In [80]: seq[-6:-2] Out[80]: [3, 6, 3, 6]
切片语义需要一点时间来适应,特别是如果你是从 R 或 MATLAB 过来的。参见图 3.1 以了解使用正整数和负整数进行切片的有用示例。在图中,索引显示在“箱边缘”,以帮助显示使用正整数或负整数索引开始和停止的切片选择。
图 3.1:Python 切片约定的示例
第二个冒号后也可以使用step
,比如,每隔一个元素取一个:
In [81]: seq[::2] Out[81]: [7, 3, 3, 0]
这种方法的一个巧妙用法是传递-1
,这样可以有效地反转列表或元组:
In [82]: seq[::-1] Out[82]: [1, 0, 6, 3, 6, 3, 2, 7]
字典
字典或dict
可能是 Python 中最重要的内置数据结构。在其他编程语言中,字典有时被称为哈希映射或关联数组。字典存储一组键-值对,其中键和值是 Python 对象。每个键都与一个值关联,以便可以方便地检索、插入、修改或删除给定特定键的值。创建字典的一种方法是使用大括号{}
和冒号来分隔键和值:
In [83]: empty_dict = {} In [84]: d1 = {"a": "some value", "b": [1, 2, 3, 4]} In [85]: d1 Out[85]: {'a': 'some value', 'b': [1, 2, 3, 4]}
可以使用与访问列表或元组元素相同的语法来访问、插入或设置元素:
In [86]: d1[7] = "an integer" In [87]: d1 Out[87]: {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'} In [88]: d1["b"] Out[88]: [1, 2, 3, 4]
你可以使用与检查列表或元组是否包含值相同的语法来检查字典是否包含键:
In [89]: "b" in d1 Out[89]: True
可以使用del
关键字或pop
方法(同时返回值并删除键)来删除值:
In [90]: d1[5] = "some value" In [91]: d1 Out[91]: {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer', 5: 'some value'} In [92]: d1["dummy"] = "another value" In [93]: d1 Out[93]: {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer', 5: 'some value', 'dummy': 'another value'} In [94]: del d1[5] In [95]: d1 Out[95]: {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer', 'dummy': 'another value'} In [96]: ret = d1.pop("dummy") In [97]: ret Out[97]: 'another value' In [98]: d1 Out[98]: {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}
keys
和values
方法分别为你提供字典的键和值的迭代器。键的顺序取决于它们插入的顺序,这些函数以相同的顺序输出键和值:
In [99]: list(d1.keys()) Out[99]: ['a', 'b', 7] In [100]: list(d1.values()) Out[100]: ['some value', [1, 2, 3, 4], 'an integer']
如果需要同时迭代键和值,可以使用items
方法以 2 元组的形式迭代键和值:
In [101]: list(d1.items()) Out[101]: [('a', 'some value'), ('b', [1, 2, 3, 4]), (7, 'an integer')]
可以使用update
方法将一个字典合并到另一个字典中:
In [102]: d1.update({"b": "foo", "c": 12}) In [103]: d1 Out[103]: {'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}
update
方法会直接更改字典,因此传递给update
的数据中的任何现有键都将丢弃其旧值。
从序列创建字典
通常会偶尔出现两个你想要逐个元素配对的序列。作为第一步,你可能会编写这样的代码:
mapping = {} for key, value in zip(key_list, value_list): mapping[key] = value
由于字典本质上是 2 元组的集合,dict
函数接受一个 2 元组的列表:
In [104]: tuples = zip(range(5), reversed(range(5))) In [105]: tuples Out[105]: <zip at 0x17d604d00> In [106]: mapping = dict(tuples) In [107]: mapping Out[107]: {0: 4, 1: 3, 2: 2, 3: 1, 4: 0}
稍后我们将讨论字典推导,这是构建字典的另一种方法。
默认值
通常会有类似以下逻辑:
if key in some_dict: value = some_dict[key] else: value = default_value
因此,字典方法get
和pop
可以接受要返回的默认值,因此上述if-else
块可以简单地写为:
value = some_dict.get(key, default_value)
get
默认情况下会返回None
,如果键不存在,而pop
会引发异常。在设置值时,可能字典中的值是另一种集合,比如列表。例如,你可以想象将单词列表按照它们的首字母分类为列表的字典:
In [108]: words = ["apple", "bat", "bar", "atom", "book"] In [109]: by_letter = {} In [110]: for word in words: .....: letter = word[0] .....: if letter not in by_letter: .....: by_letter[letter] = [word] .....: else: .....: by_letter[letter].append(word) .....: In [111]: by_letter Out[111]: {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
setdefault
字典方法可用于简化此工作流程。前面的for
循环可以重写为:
In [112]: by_letter = {} In [113]: for word in words: .....: letter = word[0] .....: by_letter.setdefault(letter, []).append(word) .....: In [114]: by_letter Out[114]: {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
内置的collections
模块有一个有用的类defaultdict
,使这更加容易。要创建一个,你需要传递一个类型或函数,用于为字典中的每个槽生成默认值:
In [115]: from collections import defaultdict In [116]: by_letter = defaultdict(list) In [117]: for word in words: .....: by_letter[word[0]].append(word)
有效的字典键类型
虽然字典的值可以是任何 Python 对象,但键通常必须是不可变对象,如标量类型(int、float、string)或元组(元组中的所有对象也必须是不可变的)。这里的技术术语是可哈希性。你可以使用hash
函数检查对象是否可哈希(可以用作字典中的键):
In [118]: hash("string") Out[118]: 4022908869268713487 In [119]: hash((1, 2, (2, 3))) Out[119]: -9209053662355515447 In [120]: hash((1, 2, [2, 3])) # fails because lists are mutable --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-120-473c35a62c0b> in <module> ----> 1 hash((1, 2, [2, 3])) # fails because lists are mutable TypeError: unhashable type: 'list'
通常情况下,使用hash
函数时看到的哈希值将取决于你使用的 Python 版本。
要将列表用作键,一种选择是将其转换为元组,只要其元素也可以被散列:
In [121]: d = {} In [122]: d[tuple([1, 2, 3])] = 5 In [123]: d Out[123]: {(1, 2, 3): 5}
集合
集合是一个无序的唯一元素集合。可以通过set
函数或使用花括号的集合字面值来创建集合:
In [124]: set([2, 2, 2, 1, 3, 3]) Out[124]: {1, 2, 3} In [125]: {2, 2, 2, 1, 3, 3} Out[125]: {1, 2, 3}
集合支持数学集合操作,如并集、交集、差集和对称差集。考虑这两个示例集合:
In [126]: a = {1, 2, 3, 4, 5} In [127]: b = {3, 4, 5, 6, 7, 8}
这两个集合的并集是两个集合中出现的不同元素的集合。可以使用union
方法或|
二进制运算符来计算:
In [128]: a.union(b) Out[128]: {1, 2, 3, 4, 5, 6, 7, 8} In [129]: a | b Out[129]: {1, 2, 3, 4, 5, 6, 7, 8}
交集包含两个集合中都出现的元素。可以使用&
运算符或intersection
方法:
In [130]: a.intersection(b) Out[130]: {3, 4, 5} In [131]: a & b Out[131]: {3, 4, 5}
请参见表 3.1 以获取常用集合方法的列表。
表 3.1:Python 集合操作
函数 | 替代语法 | 描述 |
a.add(x) |
N/A | 将元素x 添加到集合a 中 |
a.clear() |
N/A | 将集合a 重置为空状态,丢弃所有元素 |
a.remove(x) |
N/A | 从集合a 中删除元素x |
a.pop() |
N/A | 从集合a 中删除一个任意元素,如果集合为空则引发KeyError |
a.union(b) |
a | b |
a 和b 中所有唯一的元素 |
a.update(b) |
a |= b |
将a 的内容设置为a 和b 中元素的并集 |
a.intersection(b) |
a & b |
a 和b 中都存在的所有元素 |
a.intersection_update(b) |
a &= b |
将a 的内容设置为a 和b 中元素的交集 |
a.difference(b) |
a - b |
a 中不在b 中的元素 |
a.difference_update(b) |
a -= b |
将a 设置为a 中不在b 中的元素 |
a.symmetric_difference(b) |
a ^ b |
a 或b 中的所有元素,但不是两者都有的 |
a.symmetric_difference_update(b) |
a ^= b |
将a 设置为a 或b 中的元素,但不是两者都有的 |
a.issubset(b) |
<= |
如果a 的元素都包含在b 中,则为True |
a.issuperset(b) |
>= |
如果b 的元素都包含在a 中,则为True |
a.isdisjoint(b) |
N/A | 如果a 和b 没有共同元素,则为True |
注意
如果将不是集合的输入传递给union
和intersection
等方法,Python 将在执行操作之前将输入转换为集合。在使用二进制运算符时,两个对象必须已经是集合。
所有逻辑集合操作都有原地对应物,这使您可以用结果替换操作左侧集合的内容。对于非常大的集合,这可能更有效率:*
In [132]: c = a.copy() In [133]: c |= b In [134]: c Out[134]: {1, 2, 3, 4, 5, 6, 7, 8} In [135]: d = a.copy() In [136]: d &= b In [137]: d Out[137]: {3, 4, 5}
与字典键类似,集合元素通常必须是不可变的,并且它们必须是可散列的(这意味着对值调用hash
不会引发异常)。为了将类似列表的元素(或其他可变序列)存储在集合中,可以将它们转换为元组:
In [138]: my_data = [1, 2, 3, 4] In [139]: my_set = {tuple(my_data)} In [140]: my_set Out[140]: {(1, 2, 3, 4)}
您还可以检查一个集合是否是另一个集合的子集(包含在内)或超集(包含所有元素):
In [141]: a_set = {1, 2, 3, 4, 5} In [142]: {1, 2, 3}.issubset(a_set) Out[142]: True In [143]: a_set.issuperset({1, 2, 3}) Out[143]: True
只有当集合的内容相等时,集合才相等:
In [144]: {1, 2, 3} == {3, 2, 1} Out[144]: True
内置序列函数
Python 有一些有用的序列函数,您应该熟悉并在任何机会使用。
enumerate
在迭代序列时,通常希望跟踪当前项目的索引。自己动手的方法如下:
index = 0 for value in collection: # do something with value index += 1
由于这种情况很常见,Python 有一个内置函数enumerate
,它返回一个(i, value)
元组序列:
for index, value in enumerate(collection): # do something with value
sorted
sorted
函数从任何序列的元素返回一个新的排序列表:
In [145]: sorted([7, 1, 2, 6, 0, 3, 2]) Out[145]: [0, 1, 2, 2, 3, 6, 7] In [146]: sorted("horse race") Out[146]: [' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's']
sorted
函数接受与列表的sort
方法相同的参数。
zip
zip
将多个列表、元组或其他序列的元素“配对”起来,以创建一个元组列表:
In [147]: seq1 = ["foo", "bar", "baz"] In [148]: seq2 = ["one", "two", "three"] In [149]: zipped = zip(seq1, seq2) In [150]: list(zipped) Out[150]: [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]
zip
可以接受任意数量的序列,并且它生成的元素数量由最短的序列决定:
In [151]: seq3 = [False, True] In [152]: list(zip(seq1, seq2, seq3)) Out[152]: [('foo', 'one', False), ('bar', 'two', True)]
zip
的一个常见用法是同时迭代多个序列,可能还与enumerate
结合使用:
In [153]: for index, (a, b) in enumerate(zip(seq1, seq2)): .....: print(f"{index}: {a}, {b}") .....: 0: foo, one 1: bar, two 2: baz, three
反转
reversed
以相反的顺序迭代序列的元素:
In [154]: list(reversed(range(10))) Out[154]: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
请记住,reversed
是一个生成器(稍后将更详细讨论),因此它不会创建反转的序列,直到实现(例如,使用list
或for
循环)。
列表、集合和字典推导
列表推导是 Python 语言中一个方便且广泛使用的特性。它们允许您通过过滤集合的元素,将通过过滤的元素转换为一个简洁的表达式来简洁地形成一个新列表。它们的基本形式如下:
[expr for value in collection if condition]
这等同于以下的for
循环:
result = [] for value in collection: if condition: result.append(expr)
过滤条件可以被省略,只留下表达式。例如,给定一个字符串列表,我们可以过滤出长度为2
或更少的字符串,并将它们转换为大写:
In [155]: strings = ["a", "as", "bat", "car", "dove", "python"] In [156]: [x.upper() for x in strings if len(x) > 2] Out[156]: ['BAT', 'CAR', 'DOVE', 'PYTHON']
集合和字典推导是一个自然的扩展,以一种类似的方式产生集合和字典,而不是列表。
字典推导看起来像这样:
dict_comp = {key-expr: value-expr for value in collection if condition}
集合推导看起来与等效的列表推导相同,只是用花括号代替方括号:
set_comp = {expr for value in collection if condition}
与列表推导类似,集合和字典推导大多是便利性的,但它们同样可以使代码更易于编写和阅读。考虑之前的字符串列表。假设我们想要一个集合,其中只包含集合中包含的字符串的长度;我们可以很容易地使用集合推导来计算:
In [157]: unique_lengths = {len(x) for x in strings} In [158]: unique_lengths Out[158]: {1, 2, 3, 4, 6}
我们也可以更加功能化地使用map
函数,稍后介绍:
In [159]: set(map(len, strings)) Out[159]: {1, 2, 3, 4, 6}
作为一个简单的字典推导示例,我们可以创建一个查找这些字符串在列表中位置的查找映射:
In [160]: loc_mapping = {value: index for index, value in enumerate(strings)} In [161]: loc_mapping Out[161]: {'a': 0, 'as': 1, 'bat': 2, 'car': 3, 'dove': 4, 'python': 5}
嵌套列表推导
假设我们有一个包含一些英文和西班牙名字的列表列表:
In [162]: all_data = [["John", "Emily", "Michael", "Mary", "Steven"], .....: ["Maria", "Juan", "Javier", "Natalia", "Pilar"]]
假设我们想要获得一个包含所有包含两个或更多个a
的名称的单个列表。我们可以通过一个简单的for
循环来实现:
In [163]: names_of_interest = [] In [164]: for names in all_data: .....: enough_as = [name for name in names if name.count("a") >= 2] .....: names_of_interest.extend(enough_as) .....: In [165]: names_of_interest Out[165]: ['Maria', 'Natalia']
实际上,您可以将整个操作封装在一个单独的嵌套列表推导中,看起来像:
In [166]: result = [name for names in all_data for name in names .....: if name.count("a") >= 2] In [167]: result Out[167]: ['Maria', 'Natalia']
起初,嵌套列表推导可能有点难以理解。列表推导的for
部分按照嵌套的顺序排列,任何过滤条件都放在最后。这里是另一个示例,我们将整数元组的列表“展平”为一个简单的整数列表:
In [168]: some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)] In [169]: flattened = [x for tup in some_tuples for x in tup] In [170]: flattened Out[170]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
请记住,如果您写一个嵌套的for
循环而不是列表推导,for
表达式的顺序将是相同的:
flattened = [] for tup in some_tuples: for x in tup: flattened.append(x)
您可以有任意多层的嵌套,尽管如果您有超过两三层的嵌套,您可能应该开始质疑这是否在代码可读性方面是有意义的。重要的是要区分刚刚显示的语法与列表推导内部的列表推导,后者也是完全有效的:
In [172]: [[x for x in tup] for tup in some_tuples] Out[172]: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
这将产生一个列表的列表,而不是所有内部元素的扁平化列表。
Python 数据分析(PYDA)第三版(一)(4)https://developer.aliyun.com/article/1482371