目录
一、列表解析式
列表解析式(List Comprehension)是 Python 中一种简洁而强大的创建列表的方式。它通过一个表达式和一个或多个 for 循环来生成列表,并且可以包含条件判断。相比于传统的 for 循环,列表解析式可以使代码更加简洁和易读,列表解析式也叫列表推导式。 基本结构:
[表达式 for 元素 in 可迭代对象 if 条件]
# 1.表达式: 对每个元素执行的操作,可以是简单的操作,也可以是复杂的函数调用
# 2.for 元素 in 可迭代对象:遍历一个可迭代对象,如列表、元组、字典等
# 3.if 条件: 可选部分,只有满足条件的元素才会被添加到新列表中
举例:嵌套循环和条件过滤。 假设我们有一个矩阵(二维列表),并且我们想要提取矩阵中每个元素的平方值,只有当这个元素是偶数时,才将其平方添加到新的列表中。数据:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
使用列表解析式:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
squared_evens = [x ** 2 for row in matrix for x in row if x % 2 == 0]
print(squared_evens)
# 解析步骤
# 1.遍历矩阵: for row in matrix,每次循环处理一个子列表(即矩阵中的一行)
# 2.遍历行中的元素: for x in row,每次循环处理子列表中的一个元素。
# 3.条件判断: if x % 2 == 0,仅当元素是偶数时,才会执行表达式。
# 4.表达式: x**2,对满足条件的元素求平方。
基于一个字符串构建一个 Unicode 码点列表:
# 示例1.1
symbols = '$¢£¥€¤'
codes = []
for symbol in symbols:
codes.append(ord(symbol))
print(codes) # [36, 162, 163, 165, 8364, 164]
# 示例1.2 用列表推导式基于一个字符串构建一个 Unicode 码点列表
codes = [ord(symbol) for symbol in symbols]
print(codes)
稍微懂一点 Python 或许就能看懂示例 示例1.1,但是学会列表推导式后,我觉得 示例1.2 更易于理解,因其意图是明确的。for 循环能胜任许多任务,可以遍历一个序列,统计项数或挑选部分项,可以计算总数和平均数,等等。示例1.1 使用 for 循环构建一个列表。当然,如果滥用列表推导式,写出的代码也不一定易于理解。我曾经见过一段 Python 代码,仅仅为了得到副作用,就用列表推导式重复执行代码段。如果你不打算使用生成的列表,那就不要使用列表推导式句法。另外,列表推导式应保持简短。如果超过两行,那么最好把语句拆开,或者使用传统的 for 循环重写。写 Python 代码就跟写文章一样,没有什么硬性规则,这个尺度你得自己把握。
进一步复杂化:多重条件与函数应用。假设我们想要从一个包含字典的列表中提取特定条件的值,并对其进行处理。每个字典代表一个学生的信息,包含学生的姓名和分数。我们想要提取分数大于 80
且名字长度小于 5
的学生,并将他们的名字转换为大写。示例:
# 学生列表
students = [
{'name': 'Alice', 'score': 85},
{'name': 'Bob', 'score': 92},
{'name': 'Charlie', 'score': 78},
{'name': 'Dave', 'score': 88}
]
high_score_students = [student['name'].upper() for student in students if
student['score'] > 80 and len(student['name']) < 5]
print(high_score_students) # ['BOB', 'DAVE']
# 1.遍历学生列表: for student in students,每次循环处理一个字典
# 2.条件判断: if student['score'] > 80 and len(student['name']) < 5,仅当分数大于80且名字长度小于5时,
# 才会执行表达式。
# 3.表达式: student['name'].upper(),将满足条件的学生名字转换为大写。
其他示例:
import random
list1 = [i for i in range(10)] # 创建0~10之间(不包括10)的数字列表
print(list1)
list2 = [i for i in range(10, 100, 10)] # 创建10~100之间(不包括100)的整十数列表
print(list2)
# 创建10个4位数的随机数列表
list3 = [random.randint(1000, 10000) for i in range(10)]
print(list3)
list4 = [i for i in '壹贰叁肆伍'] # 将字符串转换为列表
print(list4)
# 生成所有单词首字母列表
list5 = [i[0] for i in ['Early', 'bird', 'gets', 'the', 'worm']]
print(list5)
# 将原列表中的数字折半后生成新的列表
list6 = [int(i * 0.5) for i in [1200, 5608, 4314, 6060, 5210]]
print(list6)
list7 = [i for i in ('Early', 'bird', 'gets', 'the', 'worm')] # 通过元组生成新的列表
print(list7)
# 将字典的Key生成新的列表
list8 = [key for key in {'name': 'amo', 'age': 18, 'male': True}]
print(list8)
list9 = [key for key in {1, 3, 5, 7, 9}] # 通过集合生成有序列表
print(list9)
# 三种写法哪种更好呢?
'''
[expr for item in iterable if cond1 if cond2]
等价于
ret = []
for item in iterable:
if cond1:
if cond2:
ret.append(expr)
[expr for i in iterable1 for j in iterable2 ]
等价于
ret = []
for i in iterable1:
for j in iterable2:
ret.append(expr)
'''
print([(i, j) for i in range(7) if i > 4 for j in range(20, 25) if j > 23])
print([(i, j) for i in range(7) for j in range(20, 25) if i > 4 if j > 23])
print([(i, j) for i in range(7) for j in range(20, 25) if i > 4 and j > 23]) # [(5, 24), (6, 24)]
列表解析式就是一种语法糖,编译器会优化,不会因为简写而影响效率,反而因优化提高了效率,减少程序员工作量,减少出错,简化了代码,增强了可读性。如果你很少使用,那可真是遗憾。
二、集合解析式
集合解析式(Set Comprehension)与列表解析式类似,用于生成集合。集合是一个无序且不允许重复元素的数据结构。集合解析式的语法与列表解析式相同,只是用大括号 {}
代替了方括号 []
。集合解析式特别适用于需要去重的场景。基本结构:
{表达式 for 元素 in 可迭代对象 if 条件}
# 1.表达式: 对每个元素执行的操作。
# 2.for 元素 in 可迭代对象: 遍历一个可迭代对象,如列表、元组、字典等。
# 3.if 条件: 可选部分,只有满足条件的元素才会被添加到集合中。
示例代码:
import random
# 通过集合推导式生成集合
set1 = {i for i in range(10)} # 创建0~10之间(不包括10)的数字集合
print(set1)
set2 = {i for i in range(2, 10, 2)} # 创建10以内(不包括10)的偶数集合
print(set2)
# 创建无重复的4位随机数集合
set3 = {random.randint(1000, 10000) for i in range(10)}
print(set3)
set4 = {i for i in '壹贰叁肆伍'} # 将字符串转换为集合
print(set4)
# 生成所有单词首字母集合
set5 = {i[0] for i in ['Early', 'bird', 'gets', 'the', 'worm']}
print(set5)
# 将原集合中的数字折半后生成新的集合
set6 = {int(i * 0.5) for i in {1200, 5608, 4314, 6060, 5210}}
print(set6)
set7 = {i for i in ('Early', 'bird', 'gets', 'the', 'worm')} # 通过元组生成新的集合
print(set7)
# 将字典的Key生成新的集合
set8 = {key for key in {'name': 'amo', 'age': 18, 'address': '重庆市'}}
print(set8)
# 将字典的Value生成新的集合
set9 = {v for k, v in {'name': 'amo', 'age': 18, 'address': '重庆市'}.items()}
print(set9)
letters = ['A', 'B', 'C']
numbers = [1, 2, 3]
cartesian_product_set = {(letter, number) for letter in letters for number in numbers}
print(cartesian_product_set)
numbers = [1, 2, 2, 3, 4, 4, 5]
squared_set = {x ** 2 for x in numbers}
print(squared_set)
even_squared_set = {x ** 2 for x in numbers if x % 2 == 0}
print(even_squared_set)
print({(x, x + 1) for x in range(10)})
print({[x] for x in range(10)}) # 可以吗? 可以个der TypeError: unhashable type: 'list'
集合解析式提供了一种简单而强大的方式来生成集合,特别适合处理需要去重的情况。通过结合多循环和条件过滤,集合解析式可以在一行代码中实现复杂的逻辑操作,同时保持代码的简洁和可读性。
三、字典解析式
字典解析式(Dictionary Comprehension)是一种简洁而优雅的创建字典的方式。它允许你在一行代码中使用一个表达式来生成字典,而不需要使用多行的循环。字典解析式的语法与列表解析式和集合解析式类似,只是它生成的是一个键值对的集合。基本结构:
{键表达式: 值表达式 for 元素 in 可迭代对象 if 条件}
# 1.键表达式: 生成字典中每个键的表达式。
# 2.值表达式: 生成字典中每个值的表达式。
# 3.for 元素 in 可迭代对象:遍历一个可迭代对象,如列表、元组、字典等。
# 4.if 条件: 可选部分,只有满足条件的元素才会被包含在字典中。
示例代码:
# 1.简单的字典解析式
words = ['apple', 'banana', 'cherry']
word_length_dict = {word: len(word) for word in words}
print(word_length_dict)
# 输出: {'apple': 5, 'banana': 6, 'cherry': 6}
# 2.带条件的字典解析式
filtered_word_length_dict = {word: len(word) for word in words if len(word) > 5}
print(filtered_word_length_dict)
# 多循环的字典解析式
letters = ['A', 'B', 'C']
numbers = [1, 2, 3]
cartesian_product_dict = {letter: number for letter in letters for number in numbers}
print(cartesian_product_dict) # 由于字典中的键必须唯一,上述代码会不断覆盖之前的值,
# 输出: {'A': 3, 'B': 3, 'C': 3}
cartesian_product_dict = {(letter, number): letter + str(number)
for letter in letters for number in numbers}
print(cartesian_product_dict)
# 3.从现有字典生成新字典
inventory = {'apple': 2, 'banana': 3, 'cherry': 5}
squared_inventory = {item: quantity ** 2 for item, quantity in inventory.items()}
print(squared_inventory) # {'apple': 4, 'banana': 9, 'cherry': 25}
filtered_inventory = {item: quantity for item, quantity in inventory.items() if quantity > 2}
print(filtered_inventory)
print({x: (x, x + 1) for x in range(10)})
print({x: [x, x + 1] for x in range(10)})
print({(x,): [x, x + 1] for x in range(10)})
# print({[x]: [x, x + 1] for x in range(10)}) TypeError: unhashable type: 'list'
# {'0': 3, '1': 3, '2': 3}
print({str(x): y for x in range(3) for y in range(4)}) # 输出多个元素?
字典解析式是 Python 中非常强大且简洁的特性之一。通过它,你可以轻松地创建和转换字典,并且可以使用多循环和条件判断来实现复杂的逻辑操作,同时保持代码简洁明了。
四、生成器表达式
生成器表达式(Generator Expression)是 Python 中用于创建生成器的一种简洁语法。生成器是一种特殊的迭代器,它允许你逐个生成元素,而不是一次性生成所有元素,从而节省内存。生成器表达式的语法类似于列表解析式,但使用圆括号 ()
而不是方括号 []
。
(表达式 for 元素 in 可迭代对象 if 条件)
# 1.表达式: 用于生成每个元素的操作
# 2.for 元素 in 可迭代对象:遍历一个可迭代对象
# 3.if 条件: 可选部分,只有满足条件的元素才会被生成
示例代码:
# 1.简单的生成器表达式
numbers = [1, 2, 3, 4, 5]
squared_gen = (x ** 2 for x in numbers)
print(squared_gen) # <generator object <genexpr> at 0x000001EB11D404A0>
print(type(squared_gen)) # <class 'generator'>
# 访问生成器中的元素
for square in squared_gen:
print(square)
for square in squared_gen:
print(square)
# 2.带条件的生成器表达式
even_squared_gen = (x ** 2 for x in numbers if x % 2 == 0)
for square in even_squared_gen:
print(square)
for square in even_squared_gen:
print(square)
# 3.将生成器转换为列表或其他容器
squared_list = list(x ** 2 for x in numbers)
print(squared_list)
# 输出: [1, 4, 9, 16, 25]
使用生成器表达式时,有一些注意事项和最佳实践可以帮助你避免常见的陷阱,并确保代码高效、可读。
-
一次性消费。注意:生成器只能遍历一次,一旦你遍历了生成器中的元素,它们就消失了。示例:
gen = (x ** 2 for x in range(5)) list1 = list(gen) list2 = list(gen) # 这里会得到一个空列表,因为生成器已经被消费了 print(list1) # 输出: [0, 1, 4, 9, 16] print(list2) # 输出: [] # 解决方法:如果你需要多次访问生成器中的数据,可以先将其转换为列表或元组
-
生成器不能倒退。注意:生成器是一次性、顺序的,不能倒退或重新开始。你只能前进到下一个元素。解决方法:如果需要重复访问数据,考虑将生成器的结果存储在列表或其他容器中。
-
延迟计算。注意:生成器表达式是惰性求值的,意味着它们不会立即计算结果,直到你显式请求数据(例如,通过 next() 或循环遍历)。示例:
gen = (x ** 2 for x in range(5)) print(next(gen)) # 输出: 0 print(next(gen)) # 输出: 1 print(next(gen)) # 输出: 4 print(next(gen)) # 输出: 9 print(next(gen)) # 输出: 16 # print(next(gen)) # 输出: StopIteration print(next(gen, 8)) # 8
应用场景:这对于处理大型数据集或流式数据非常有用,因为它允许你按需生成数据,节省内存。
-
避免与列表解析式混淆。注意:生成器表达式和列表解析式的语法非常相似,但它们的行为不同。生成器使用
圆括号 ()
而列表解析式使用方括号 []
。解决方法:确保你根据具体需求选择合适的表达式。gen = (x ** 2 for x in range(5)) # 生成器表达式 lst = [x ** 2 for x in range(5)] # 列表解析式 print(gen) print(lst)
-
效率与内存管理。注意:生成器在处理大型数据集时内存效率更高,但对于较小的数据集,如果需要频繁访问,可能不如列表高效。解决方法:根据数据集的大小和访问频率选择生成器还是列表解析式。
-
与其他迭代器的组合使用。注意:生成器可以与其他迭代器(如 map()、filter() 等)组合使用,但要注意顺序和逻辑。示例:
# 1.生成器与 map() 结合 # 假设有一个列表,想对每个元素求平方 numbers = [1, 2, 3, 4, 5] # 使用生成器表达式生成每个元素的平方 squared_gen = (x ** 2 for x in numbers) # 使用 map 将生成器中的元素乘以 2 doubled_gen = map(lambda x: x * 2, squared_gen) print(doubled_gen, type(doubled_gen)) for value in doubled_gen: print(value) # 2.与 filter() 组合使用 # 假设有一个列表,想生成其中偶数的平方 numbers = [1, 2, 3, 4, 5] # 使用生成器表达式生成每个元素的平方 squared_gen = (x ** 2 for x in numbers) # 使用 filter 筛选出偶数的平方 even_squared_gen = filter(lambda x: x % 2 == 0, squared_gen) print(even_squared_gen, type(even_squared_gen)) for value in even_squared_gen: print(value) # 3.与 zip() 组合使用 # 假设有两个生成器表达式,想要将它们的元素配对 numbers = [1, 2, 3, 4, 5] squared_gen = (x ** 2 for x in numbers) cubed_gen = (x ** 3 for x in numbers) # 使用 zip 将两个生成器的元素配对 paired_gen = zip(squared_gen, cubed_gen) print(paired_gen, type(paired_gen)) for pair in paired_gen: print(pair)
最佳实践:在组合多个生成器和迭代器时,确保每个生成器都在需要的地方生成数据,避免意外的顺序问题。
-
异常处理。注意:生成器在计算元素时可能会抛出异常,这些异常可能会在你请求数据时才发生。解决方法:在使用生成器时做好异常处理,特别是在流式处理或处理未知数据时。
def my_gen(): for i in range(5): if i == 3: raise ValueError("An error occurred") yield i try: gen = my_gen() for value in gen: print(value) except ValueError as e: print(f"Caught an exception: {e}")
-
不适合需要随机访问的场景。注意:生成器不支持随机访问(如通过索引直接访问某个元素),它们只能按顺序生成元素。解决方法:如果需要随机访问数据,请使用列表、元组或其他支持索引的数据结构。
五、生成器表达式和列表解析式对比
- 计算方式
- 生成器表达式延迟计算,列表解析式立即计算
- 内存占用
- 单从返回值本身来说,生成器表达式省内存,列表解析式返回新的列表
- 生成器没有数据,内存占用极少,使用的时候,一次返回一个数据,只会占用一个数据的空间
- 列表解析式构造新的列表需要为所有元素立即占用掉内存
- 计算速度
- 单看计算时间看,生成器表达式耗时非常短,列表解析式耗时长
- 但生成器本身并没有返回任何值,只返回了一个生成器对象
- 列表解析式构造并返回了一个新的列表
- 列表解析式:适用于需要立即访问所有生成元素的场景。适合处理较小的数据集。如果你需要对生成的序列多次访问或操作,使用列表解析式更方便。
- 生成器表达式:适用于处理大数据集、流式数据或无限序列的场景,因其内存效率高。适合数据处理过程中的惰性求值场景。如果数据不需要重复访问,生成器表达式更为高效。
六、生成器、迭代器、可迭代对象之间的关系(学习到了后面会深入讲解,此处只作为了解)
生成器(Generator)、迭代器(Iterator)、和可迭代对象(Iterable)是 Python 中用于处理序列和数据流的核心概念。它们之间有着密切的关系,每个概念在 Python 的迭代机制中扮演不同的角色。以下是对它们之间关系的详细解释:
6.1 可迭代对象(Iterable)
可迭代对象是一个实现了 __iter__()
方法或包含 __getitem__()
方法的对象,允许我们使用 for 循环遍历其元素。常见的可迭代对象包括列表、元组、字符串、字典、集合等。特点:__iter__()
方法:可迭代对象必须实现 __iter__()
方法,该方法返回一个迭代器对象。可迭代性:只要一个对象实现了 __iter__()
方法或者 __getitem__()
方法(在早期 Python 版本中),它就是可迭代的。示例:
my_list = [1, 2, 3]
my_string = "hello"
# 这两个都是可迭代对象
for element in my_list:
print(element)
for char in my_string:
print(char)
6.2 迭代器(Iterator)
迭代器 是一个实现了 __iter__()
和 __next__()
方法的对象,__next__()
方法返回序列中的下一个元素。迭代器是一个状态机器,可以通过 next() 函数获取下一个元素,当没有元素可返回时,会引发 StopIteration 异常。特点:
- 惰性求值:迭代器一次返回一个元素,而不是一次性返回所有元素,节省内存。
- 单次使用:迭代器通常只能遍历一次,遍历完后就不能再次遍历。
- 实现方式:迭代器可以通过实现
__iter__()
和__next__()
方法的类来创建,或通过内置的 iter() 函数将可迭代对象转化为迭代器。
示例:
my_list = [1, 2, 3]
my_iter = iter(my_list) # 获取一个迭代器
print(next(my_iter)) # 输出: 1
print(next(my_iter)) # 输出: 2
print(next(my_iter)) # 输出: 3
# print(next(my_iter)) # 如果再调用 next 会引发 StopIteration 异常
6.3 生成器(Generator)
生成器 是一种特殊类型的迭代器,它通过使用 yield 关键字的函数或生成器表达式创建。生成器函数一次返回一个元素,并在 yield 处暂停,保留其状态以便下次继续执行。生成器函数自动实现了 __iter__()
和 __next__()
方法,因此生成器本质上是迭代器。特点:
- 状态保存:生成器在每次 yield 时保存状态,并在下次迭代时从暂停处继续执行。
- 语法简洁:生成器的语法比手动实现迭代器要简洁得多,适合用于生成简单序列。
- 惰性求值:生成器只在需要时才生成数据,适合处理大量数据或无限序列。
示例:
def my_generator():
yield 1
yield 2
yield 3
gen = my_generator()
for value in gen:
print(value)
6.4 三者之间的关系
- 可迭代对象(Iterable):它是一个实现了
__iter__()
方法的对象,__iter__()
返回一个迭代器。实例:列表、元组、字符串、字典、集合等。 - 迭代器(Iterator):迭代器是一个实现了
__iter__()
和__next__()
方法的对象。迭代器对象也是可迭代对象,但它通过__next__()
方法一次生成一个元素。实例:调用 iter() 函数得到的对象、生成器。迭代器未必是生成器: 虽然生成器是迭代器,但并不是所有迭代器都是生成器。迭代器可以通过类实现,而生成器通常通过 yield 创建。 - 生成器(Generator):生成器是迭代器的一种特殊形式,通常通过生成器函数(使用 yield 关键字)或生成器表达式创建。生成器也是可迭代对象和迭代器。实例:定义一个包含 yield 的函数,或使用生成器表达式。
6.5 总结与类比
可迭代对象是容器:任何可以返回一个迭代器的对象都是可迭代对象。它是数据的容器。
迭代器是序列的读取器:迭代器从可迭代对象中一个接一个地提取数据,并在没有数据时引发 StopIteration 异常。
生成器是懒惰的迭代器:生成器是一种更方便的迭代器,它在需要时生成数据,而不是一次性生成所有数据。
生成器、迭代器和可迭代对象之间的关系可以类比为一个图书馆的借书过程:
- 可迭代对象就像图书馆里的书架,书架上有很多书(元素),你可以逐本借出。
- 迭代器就像图书馆的借书员,他一次给你一本书,直到没有书可以借为止。
- 生成器就像一个自动借书机,它可以根据你的需求逐本地从书库里拿出书来,直到你不需要为止。