Python推导式

  • 列表推导式: [expression for item in iterable]
  • 集合推导式: {expression for item in iterable}
  • 字典推导式: {key_expression: value_expression for item in iterable} item是一个变量,用于遍历iterable中的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#列表推导式:
list_com = [x**2 for x in range(10) if x**2 < 40]
list_com
# [0, 1, 4, 9, 16, 25, 36]

#集合推导式 ,代表什么意思
set_com = {x**2 if x**2 % 2==0 else x**2 + 1 for x in range(10)}
set_com
# {0, 2, 4, 10, 16, 26, 36, 50, 64, 82}

#字典推导式
dict_com = {x: x**2 for x in range(10) if x != 3}
dict_com
# {0: 0, 1: 1, 2: 4, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

推导式中添加if判断的语法:

在 Python 推导式中,if判断有两种用法,分别用于过滤元素和条件赋值

  • if condition 用于过滤元素

    语法[expression for item in iterable if condition] 作用:只保留满足condition的元素,类似筛选

    1
    2
    3
    # 生成偶数列表
    evens = [x for x in range(10) if x % 2 == 0]
    # 等价于:[0, 2, 4, 6, 8]
  • if-else 表达式用于条件赋值

    语法[expression if condition else expr_else for item in iterable] 作用:根据condition的结果,为每个元素选择不同的表达式值。

    1
    2
    3
    4
    # 将负数转换为 0,正数保持不变:
    nums = [-1, 2, -3, 4]
    result = [x if x >= 0 else 0 for x in nums]
    # 等价于:[0, 2, 0, 4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# `L = ['Hello', 'World', 18, 'Apple', None]`将字符串中字母变为小写,其余元素不变。
L = ['Hello', 'World', 18, 'Apple', None]
#第一种解法:
[L[i].lower() if type(L[i]) is str else L[i] for i in range(5)]#isinstance(x, str)
#第二种解法
[x.lower() if isinstance(x, str) else x for x in L]

# `keys = ['a', 'b', 'c']` `values = [1, 2, 3]`,根据给出的keys和values,用字典推导式生成新字典。
keys = ['a', 'b', 'c']
values = [1, 2, 3]
#第一种解法
{keys[i]: values[i] for i in range(len(keys))}
#第二种解法
{k: v for k, v in zip(keys, values)}


# `words = ['hello', 'world', 'python', 'you']`,用集合推导式求出words中的元素长度都有哪些。
words = ['hello', 'world', 'python', 'you']
{len(x) for x in words}

Python迭代器和可迭代对象

  • 迭代器:它是一个对象,它实现了__iter__()__next__()方法。
  • 可迭代对象:只实现了__iter__() 方法。

__iter__() 方法返回迭代器对象本身,而 __next__() 方法返回容器中的下一个值。当没有更多的元素时,__next__()方法则抛出一个 StopIteration 异常。

原来我们如何判断一个对象是不是可迭代对象呢?

1
2
3
4
from collections.abc import Iterable, Iterator

print(isinstance([1, 2, 3], Iterable))# 使用 isinstance() 函数检查列表 [1, 2, 3] 是否是可迭代对象
print(isinstance([1, 2, 3], Iterator))# 检查这个对象是否是一个迭代器

按照定义,我们分别自己创建①可迭代对象和②迭代器对象。

1
2
3
4
5
6
7
#①
class Color():
def __init__(self):
self.colors = ['red', 'white', 'black', 'green']

def __iter__(self):
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#②
class Color(object):

def __init__(self):
self.colors = ['red', 'white', 'black', 'green']

def __iter__(self):
return self

def __next__(self):
self.index += 1
if self.index >= len(self.colors):
raise StopIteration

return self.colors[self.index]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#用类实现__iter__方法
class Color():

def __init__(self):
self.colors = ['red', 'white', 'black', 'green']

def __iter__(self):
pass
# return self

#创建对象
c1_object = Color()

# 判断是否为可迭代对象
print(isinstance(c1_object, Iterable)) # True
# 判断是否为迭代器
print(isinstance(c1_object, Iterator)) # False
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Color():

def __init__(self):
self.colors = ['red', 'white', 'black', 'green']

def __iter__(self):# 返回迭代器对象本身,使得Color类的实例成为一个迭代器
return self


def __next__(self):
self.index += 1
if self.index >= len(self.colors):
raise StopIteration

return self.colors[self.index]

#创建对象
c2_object = Color()

# 判断是否为可迭代对象
print(isinstance(c2_object, Iterable)) # True
# 判断是否为迭代器
print(isinstance(c2_object, Iterator)) # True

列表、集合和字典迭代器:

  • 用内置函数iter()生成对应的迭代器
  • 再用内置函数next()迭代数据
1
2
3
4
5
6
7
li = [1, 2, 3]   #{1: 'k', 2: 'l', 3: 'p'} #
iter_lio = iter(li)
# for i in iter_lio:
# print(i)
next(iter_lio)
next(iter_lio)
next(iter_lio)

注意事项:

  • 迭代器或生成器遍历完一遍之后,不能再次遍历;若想再次遍历需要重新生成另外的迭代器或生成器。

用for循环迭代输出列表和列表迭代器,输出的都是一样的吗?如果一样,那为什么还要使用迭代器呢?

  • 使用 for 循环迭代列表和直接使用列表迭代器,其输出结果相同,但迭代器有其独特优势。迭代器支持懒加载,按需生成数据,节省内存,特别适用于处理大数据集。它们能够维护状态,记住迭代位置,实现多次迭代,并提供可扩展性,允许自定义迭代行为。迭代器在逐行读取大文件或处理流数据时尤为有用,因为它们不需要一次性加载所有数据。总之,迭代器是 Python 中实现高效和灵活数据处理的关键工具。
1
2
3
4
5
6
7
8
9
10
11
12
13
import time

start = time.time()
li = [x for x in range(1,10)]#[1,2,3,4,5] #[x for x in range(1,6)]
for i in li:
print(i)
print(f"li:{time.time() - start} s")

start2 = time.time()
my_li = iter(li)
for j in my_li:
print(j)
print(f"my_li:{time.time() - start2} s")

常用的迭代器函数

  • range(start, stop, step)
  • zip(*iterables, strict=False) strict默认匹配元素的最短长度,多余的自动忽略
  • map(function, iterable) 将函数应用于一个或多个可迭代对象的每一项
  • enumerate(iterable, start=0) 返回一个枚举对象,start是默认的起始索引
  • isinstance(z, Iterator) 的作用是判断变量 z 是否是一个迭代器。如果是迭代器,返回 True;否则返回 False。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#zip()迭代器
li0 = [1, 2, 3]
li1 = ['a', 'b', 'c', 'd']
z = zip(li0, li1)
z0 = zip(li0, li1)
print(isinstance(z, Iterator), z)

#直接用for循环遍历
for i in z:
print('i--:', i)

#或者把迭代器放到容器中
lz = list(z0)
for j in lz:
print('j--:', j)

# 使用zip将两个列表组合
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]

for name, age in zip(names, ages):
print(f"{name} is {age} years old.")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#map()传一个可迭代对象
# import math
num = [4, 9, 1, 16, 7] #推导式

def a(x):
return x**0.5

num_map = map(a, num) #math.sqrt
list(num_map)

#map()传多个可迭代对象
num = [4, 9, 1, 16, 7]

def mysquare():
for i in range(5):
yield i**2
squa = mysquare() # 创建生成器对象squa,调用生成器函数mysquare

def add(x, y):
return x + y

add_map = map(add, num, squa) #匿名函数 lambda x, y: x+y
list(add_map)
1
2
3
4
5
6
7
8
9
#enumerate
from collections.abc import Iterable, Iterator
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
enu = enumerate(seasons) # start = 1

print(isinstance(enu, Iterator))

for i in enu:
print(i)

生成器和生成器表达式

  • 生成器是一种特殊的迭代器。
  • 生成器表达式就是推导式把外包围换成小括号。但推导式是一次生成数据,数据量太大的情况下,生成器表达式对内存更友好。
1
2
3
4
5
6
7
8
#使用yield语句\关键字,只能在函数内部定义
def reverse(data):
# 使用 for 循环遍历 data 的索引,从最后一个元素开始向前遍历到第一个元素
for index in range(len(data)-1, -1, -1):
yield data[index]

for char in reverse('Python!'):
print(char)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#生成器例子:
def generate_squares():
for i in range(4):
yield i**2 # 每次生成一个平方数,并暂停函数执行,直到下一次调用

ge = generate_squares()
# 检查ge是否是迭代器(Iterator),并打印生成器对象
print(isinstance(ge, Iterator), ge)

# 第一次运行生成器
for i in ge:
print(i)
# 第二次运行生成器
ge = generate_squares()
for i in ge:
print(i)
#或者把迭代器放到容器中
gg = list(ge)
1
2
3
4
5
6
7
#生成器表达式
la = (2**x for x in range(8))
list(la)

# 生成一个以3为底,5到60为幂次的生成器。
xx = (3**x for x in range(5, 61))
list(xx) #生成器要显示输出结果,需要用构造函数转换输出

练习:

  • str_p = ['xiAoMi', 'HUawEi', 'BaIdu', 'xUnFEi'],使用map()把列表中的字符串都变为小写字母。

    1
    2
    3
    4
    5
    str_p = ['xiAoMi', 'HUawEi', 'BaIdu', 'xUnFEi']
    def ss(s):
    return s.lower()

    p = list(map(ss, str_p))
  • dic = {'a': 11, 'b': 20, 'c': 99, 'd': 67},用enumerate()输出字典的索引(从1开始)和对应的键值对。

    1
    2
    3
    dic = {'a': 11, 'b': 20, 'c': 99, 'd': 67}
    for i, (key, value) in enumerate(dic.items(), start=1):
    print(i, key, value)
  • a1 = [1, 0, 3, 5] a2 = [2, 10, 11, 4] a3 = [3, 6, 7, 18] ,要求返回三个列表元素的总和,使用map()和zip()。

    1
    2
    3
    4
    5
    6
    7
    a1 = [1, 0, 3, 5]
    a2 = [2, 10, 11, 4]
    a3 = [3, 6, 7, 18]
    # 使用zip()函数将三个列表组合成一个元组序列
    zipped = zip(a1, a2, a3)
    # 使用map()函数对每个元组中的所有元素求和
    summed = list(map(sum, zip(a1, a2, a3)))
  • tup = (1,2,3,4,5,6,7),要求把tup元组中所有偶数位置上的元素都改为0,返回处理后的新元组。

    1
    2
    tup = (1,2,3,4,5,6,7)
    tuple(0 if i%2==0 else j for i,j in enumerate(tup))

总结

  1. 什么情况下用推导式?
  • 基于现有序列的简单变换和过滤后创建新的序列:当你需要对一个序列进行简单的变换(如乘以2、取平方等)或过滤(如只选择大于某个值的元素)时,推导式可以提供一种更简洁、更易读的方式来创建新的序列。
  • 需要一次性生成整个序列:当你需要生成的序列不是特别大,且可以一次性加载到内存中时,推导式是一个好选择。
  1. 什么情况下用生成器?
  • 处理大数据集:当你处理的数据集非常大,一次性加载到内存中不现实时,生成器可以按需生成数据,从而节省内存。
  • 需要一个按需生成元素的序列:生成器非常适合于那些元素生成过程复杂或耗时的情况,因为它可以延迟计算,直到元素实际需要时才生成。
  1. 什么情况下用迭代器?
  • 需要遍历容器中的所有元素:当你需要遍历一个容器中的所有元素时,迭代器提供了一种高效的方法。
  • 尤其是不需要一次性将所有元素加载到内存中时:迭代器允许你逐个访问元素,而不需要将整个容器加载到内存中,这对于处理大型数据集特别有用。