2024 Python3.10 系统入门+进阶(十一):Python解析式和生成器表达式

一、列表解析式

列表解析式(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]

使用生成器表达式时,有一些注意事项和最佳实践可以帮助你避免常见的陷阱,并确保代码高效、可读。

  1. 一次性消费。注意:生成器只能遍历一次,一旦你遍历了生成器中的元素,它们就消失了。示例:

    gen = (x ** 2 for x in range(5))
    list1 = list(gen)
    list2 = list(gen)  # 这里会得到一个空列表,因为生成器已经被消费了
    
    print(list1)  # 输出: [0, 1, 4, 9, 16]
    print(list2)  # 输出: []
    # 解决方法:如果你需要多次访问生成器中的数据,可以先将其转换为列表或元组
    
  2. 生成器不能倒退。注意:生成器是一次性、顺序的,不能倒退或重新开始。你只能前进到下一个元素。解决方法:如果需要重复访问数据,考虑将生成器的结果存储在列表或其他容器中。

  3. 延迟计算。注意:生成器表达式是惰性求值的,意味着它们不会立即计算结果,直到你显式请求数据(例如,通过 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
    

    应用场景:这对于处理大型数据集或流式数据非常有用,因为它允许你按需生成数据,节省内存。

  4. 避免与列表解析式混淆。注意:生成器表达式和列表解析式的语法非常相似,但它们的行为不同。生成器使用 圆括号 () 而列表解析式使用 方括号 [] 。解决方法:确保你根据具体需求选择合适的表达式。

    gen = (x ** 2 for x in range(5))  # 生成器表达式
    lst = [x ** 2 for x in range(5)]  # 列表解析式
    print(gen)
    print(lst)
    
  5. 效率与内存管理。注意:生成器在处理大型数据集时内存效率更高,但对于较小的数据集,如果需要频繁访问,可能不如列表高效。解决方法:根据数据集的大小和访问频率选择生成器还是列表解析式。

  6. 与其他迭代器的组合使用。注意:生成器可以与其他迭代器(如 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)
    

    最佳实践:在组合多个生成器和迭代器时,确保每个生成器都在需要的地方生成数据,避免意外的顺序问题。

  7. 异常处理。注意:生成器在计算元素时可能会抛出异常,这些异常可能会在你请求数据时才发生。解决方法:在使用生成器时做好异常处理,特别是在流式处理或处理未知数据时。

    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}")
    
  8. 不适合需要随机访问的场景。注意:生成器不支持随机访问(如通过索引直接访问某个元素),它们只能按顺序生成元素。解决方法:如果需要随机访问数据,请使用列表、元组或其他支持索引的数据结构。

五、生成器表达式和列表解析式对比

  1. 计算方式
    • 生成器表达式延迟计算,列表解析式立即计算
  2. 内存占用
    • 单从返回值本身来说,生成器表达式省内存,列表解析式返回新的列表
    • 生成器没有数据,内存占用极少,使用的时候,一次返回一个数据,只会占用一个数据的空间
    • 列表解析式构造新的列表需要为所有元素立即占用掉内存
  3. 计算速度
    • 单看计算时间看,生成器表达式耗时非常短,列表解析式耗时长
    • 但生成器本身并没有返回任何值,只返回了一个生成器对象
    • 列表解析式构造并返回了一个新的列表
  4. 列表解析式:适用于需要立即访问所有生成元素的场景。适合处理较小的数据集。如果你需要对生成的序列多次访问或操作,使用列表解析式更方便。
  5. 生成器表达式:适用于处理大数据集、流式数据或无限序列的场景,因其内存效率高。适合数据处理过程中的惰性求值场景。如果数据不需要重复访问,生成器表达式更为高效。

六、生成器、迭代器、可迭代对象之间的关系(学习到了后面会深入讲解,此处只作为了解)

生成器(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 异常。特点:

  1. 惰性求值:迭代器一次返回一个元素,而不是一次性返回所有元素,节省内存。
  2. 单次使用:迭代器通常只能遍历一次,遍历完后就不能再次遍历。
  3. 实现方式:迭代器可以通过实现 __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__() 方法,因此生成器本质上是迭代器。特点:

  1. 状态保存:生成器在每次 yield 时保存状态,并在下次迭代时从暂停处继续执行。
  2. 语法简洁:生成器的语法比手动实现迭代器要简洁得多,适合用于生成简单序列。
  3. 惰性求值:生成器只在需要时才生成数据,适合处理大量数据或无限序列。

示例:

def my_generator():
    yield 1
    yield 2
    yield 3


gen = my_generator()

for value in gen:
    print(value)

6.4 三者之间的关系

  1. 可迭代对象(Iterable):它是一个实现了 __iter__() 方法的对象,__iter__() 返回一个迭代器。实例:列表、元组、字符串、字典、集合等。
  2. 迭代器(Iterator):迭代器是一个实现了 __iter__()__next__() 方法的对象。迭代器对象也是可迭代对象,但它通过 __next__() 方法一次生成一个元素。实例:调用 iter() 函数得到的对象、生成器。迭代器未必是生成器: 虽然生成器是迭代器,但并不是所有迭代器都是生成器。迭代器可以通过类实现,而生成器通常通过 yield 创建。
  3. 生成器(Generator):生成器是迭代器的一种特殊形式,通常通过生成器函数(使用 yield 关键字)或生成器表达式创建。生成器也是可迭代对象和迭代器。实例:定义一个包含 yield 的函数,或使用生成器表达式。

6.5 总结与类比

可迭代对象是容器:任何可以返回一个迭代器的对象都是可迭代对象。它是数据的容器。
迭代器是序列的读取器:迭代器从可迭代对象中一个接一个地提取数据,并在没有数据时引发 StopIteration 异常。
生成器是懒惰的迭代器:生成器是一种更方便的迭代器,它在需要时生成数据,而不是一次性生成所有数据。

生成器、迭代器和可迭代对象之间的关系可以类比为一个图书馆的借书过程:

  1. 可迭代对象就像图书馆里的书架,书架上有很多书(元素),你可以逐本借出。
  2. 迭代器就像图书馆的借书员,他一次给你一本书,直到没有书可以借为止。
  3. 生成器就像一个自动借书机,它可以根据你的需求逐本地从书库里拿出书来,直到你不需要为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Amo Xiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值