python基础-操作列表和迭代器
数组基本操作
定义多个空列表
dates, highs, lows = [], [], []
创建数组并执行循环
arr = ['wu', 'bin', 'work']
for item in arr:
print(item)
range
range(min默认0, max必须指定,step默认1)
# range() 函数示例
for i in range(5): # 相当于 range(0, 5, 1)
print(i) # 输出 0 1 2 3 4
创建数字数组range
for value in range(1, 10):
print(value);
注意这里生成的数字是1-9,没有10
特别注意:
print( list( range(1,6) ) );
这里生成的结果是:[1, 2, 3, 4, 5],如果要生成1-6,那么:
print( list( range(1, 6+1) ) );
所以要谨记,使用range(min, max)生成数值的时候,如果要包含max,就必须给max + 1
如果要将创建的数字转化为数字列表(数组)
number=list(range(1,6))
print(number)
输出:[1,2,3,4,5]
range第三个参数还可以指定步长
range(2, 11, 2) // [2, , 6, 8, 10]
在python中,**表示乘方,number**2,表示Number二次方,2**3,表示2的立方
arr = []
for item in arr:
val = item**2
arr.append(val)
当然也有简便的方法
for item in arr:
arr.append( item**2 )
还可以求数组中最大值、最小值、数字和
arr = [1, 2, 3, 4]
min(arr)
max(arr)
sum(arr)
列表解析(列表推导式)
[表达式 for 变量 in 可迭代对象 if 条件]
同列表推导式。本页可搜:列表推导式。
arrs = [value**2 for value in range(1, 11)]
print(arrs);
value**2是表达式,代表平方,比如:
value = 3
value = value**2
print(value) // 9
for value in range(1,11):for循环为表达式提供值
最终arrs输出:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
其他例子:
# 生成一个包含 0 到 9 中偶数的平方的列表
even_squares = [x**2 for x in range(10) if x % 2 == 0]
print(even_squares)
# 从一个字符串列表中筛选出长度大于 3 的字符串
words = ["apple", "cat", "banana", "dog"]
long_words = [word for word in words if len(word) > 3]
print(long_words)
解包
在 Python 中,当你有一个可迭代对象(像列表、元组之类的),并且知晓该可迭代对象的元素数量时,就能使用解包赋值把可迭代对象中的元素分别赋值给不同的变量。解包赋值的基本语法为:
var1, var2, ..., varN = iterable
这里的iterable
是一个可迭代对象,var1
到varN
是要接收可迭代对象中元素的变量,而且变量的数量要和可迭代对象中元素的数量一致。
arr = [(1, 'a'), (2, 'b')]
arr1, arr2 = arr
最终,arr1
的值是(1, 'a')
,arr2
的值是(2, 'b')
。
应用示例:
# 交换变量值
a = 1
b = 2
a, b = b, a
通过解包赋值轻松实现了a
和b
值的交换。
接收函数返回的多个值:
def get_name_and_age():
return "Alice", 25
name, age = get_name_and_age()
这个例子中,函数get_name_and_age
返回一个包含姓名和年龄的元组,使用解包赋值可以把元组中的元素分别赋值给name
和age
变量。
在 Python 里,当在return
语句中使用逗号分隔多个值时,即使没有显式地使用括号,Python 也会将这些值封装成一个元组返回。也就是说,return "Alice", 25
和return ("Alice", 25)
这两种写法在功能上是等价的,最终返回的都是一个包含"Alice"
和25
这两个元素的元组。
解包运算符 *
- 在函数调用时解包参数:
*
可以把列表、元组或其他可迭代对象解包成单独的参数。
def add_numbers(a, b, c):
return a + b + c
numbers = [1, 2, 3]
result = add_numbers(*numbers)
print(result)
上面这个就是将列表中的元素拆开,分别当作单独的参数传入。
再看一个复杂点的例子:
xy_map = [
[1, 10],
[2, 20],
[3, 30],
[4, 40]
]
# 使用 zip(*xy_map) 对 xy_map 进行解包和重新组合
# *xy_map 会将 xy_map 中的子列表解包,然后 zip 函数将这些解包后的元素重新组合成元组
# 最后 [*zip(*xy_map)] 将这些元组放入一个列表中
x_unique, y_mean = [*zip(*xy_map)]
# 打印结果
print("x 唯一值:", x_unique)
print("y 均值:", y_mean)
代码解释:
xy_map
是一个包含多个子列表的列表,每个子列表包含一个x
值和对应的y
均值。zip(*xy_map)
把xy_map
中的子列表解包(xy_map解包后是[1, 10], [2, 20], [3, 30], [4, 40]
),然后zip将对应位置的元素组合成元组。然后再返回一个迭代器,这个迭代器中包含了两个元组 <迭代器 (1,2,3,4) (10,20,30,40) /迭代器>- *zip(*xy_map) 是 对上一步中zip返回的迭代器再进行解包,比如到这一步 可以使用两个参数进行解包接收,比如 x,y=*zip(*xy_map),这样x=(1,2,3,4),y=(10,20,30,40)。
[*zip(*xy_map)]
*
操作符用于对zip(*xy_map)
生成的迭代器进行解包。解包后,迭代器中的元组会被依次放入列表中,最终得到一个包含这些元组的列表[(1, 2, 3, 4), (10, 20, 30, 40)]
。。x_unique, y_mean = [*zip(*xy_map)]
把列表中的两个元组分别赋值给x_unique
和y_mean
。
输出结果:
x 唯一值: (1, 2, 3, 4)
y 均值: (10, 20, 30, 40)
切片
处理列表的部分元素,叫做切片。
基本概念
切片操作符的一般形式是[start:stop:step]
,
start
是切片的起始位置(包含该位置的元素),如果省略,则默认从列表的开头开始。stop
是切片的结束位置(不包含该位置的元素),如果省略,则默认到列表的末尾结束。step
是切片的步长,即每隔多少个元素取一个。比如[::20]
中,step
的值为20
,意味从0开始切片直到数组末尾,且着每隔20
个元素取一个。
在切片操作中,start默认值是0,表示从序列的开头开始切片;stop的默认值是序列的长度,表示切片到序列的末尾;step的默认值是1,表示每次切片的步长为1,即逐个元素进行切片。注意,切片操作中,步长不能为 0,否则会引发ValueError
异常。
my_list = [1, 2, 3, 4, 5]
print(my_list[::]) # 输出 [1, 2, 3, 4, 5],相当于从开头到末尾,步长为1进行切片
取出出列表中前3个元素 start=0 stop=3 step=1
arr = [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
print( arr[0:3] )
// [1, 4, 9]
如果是第2~4元素
print(arrs[1:4])
如果没有指定第一个索引,那么默认从0开始
print(arrs[:3])
如果从第三个元素开始~末尾,那么可以省略末尾索引
print(arrs[3:])
负数索引则从列表末尾开始计算,如下返回最后3个元素
print(arrs[-3:])
结果:[64, 81, 100]
获取列表长度
len(arrs)
切片之后的也是一个数组,也能遍历
for value in arr[:3]:
print(value)
复制列表,同时省略起止索引,[:]
arr_copy = arrs[:]
注意!arrs=arr_copy 这不是复制!!这只是让arr_copy关联到arrs指向的列表,所以这样做只是让两个变量指向同一个列表!
arr_same = arrs;
arrs.append(199)
print(arr_same) // arr_same 最后一个元素是199
使用切片反转列表
切片操作的语法是[start:stop:step]
,当step
为负数时,表示从后往前取值,利用这个特性可以实现列表的反转。
my_list=[1,2,3,4,5]reversed_list=my_list[::-1]print(reversed_list)
my_list[::-1]表示从列表的开头到末尾,以步长为-1进行切片
结果:[5,4,3,2,1]
元组
列表是可以修改的,而不可以被修改的列表叫做元组。
定义元组,要使用括号()
dimensions = (200, 50)
dimensions[0] // 读取使用
dimensions[1]
修改元组中的数据会报错
dimensions[0] = 20
// TypeError: 'tuple' object does not support item assignment
元组也可以使用for循环
dimensions = (1, 2)
for item in dimensions:
print(item)
虽然不能改变元组中的值,但是可以改变存储元组值的变量
dimensions = (1, 2)
dimensions = (3, 4 , 5) // 不会报错
列表常用方法
列表长度len
len()
是 Python 内置函数,专门用于返回对象的长度或元素个数,对于列表而言,它可以快速准确地返回列表中元素的数量。
my_list = [1, 2, 3, 4, 5]
length = len(my_list)
print(f"列表的长度是: {length}")
index查找某个元素在列表中首次出现的位置
sequence.index(value, start=0, end=len(sequence))
sequence
:这是要操作的序列对象,例如列表、元组等。value
:代表要查找的元素。start
:是可选参数,指定开始查找的索引位置,默认值为 0,也就是从序列的起始位置开始查找。end
:同样是可选参数,指定查找结束的索引位置,默认值是序列的长度,意味着查找至序列的末尾。
fruits = ["apple", "banana", "cherry", "banana"]
# 查找 "banana" 首次出现的索引
index = fruits.index("banana")
print(index)
fruits.index("banana")
会查找"banana"
在fruits
列表中首次出现的索引,输出结果为1
。
指定查找范围:
fruits = ["apple", "banana", "cherry", "banana"] // 元组也是一样
# 从索引 2 开始查找 "banana"
index = fruits.index("banana", 2)
print(index)
指定从索引 2 开始查找"banana"
,输出结果为3
。
异常处理
要查找的元素不在序列中,index()
方法会抛出ValueError
异常。
fruits = ["apple", "banana", "cherry"]
try:
index = fruits.index("date")
print(index)
except ValueError:
print("元素未找到")
统计指定元素出现次数count
count
方法可以用来统计列表中指定元素出现的次数。
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
# 统计数字 2 出现的次数
count_2 = numbers.count(2)
print(f"数字 2 在列表中出现的次数: {count_2}")
numbers.count(2)
:统计列表numbers
中元素2
出现的次数,结果为2
。代表出现了两次。
元组是 Python 中的不可变序列,同样可以使用count
方法来统计元素出现的次数。
fruits = ('apple', 'banana', 'apple', 'cherry', 'apple')
# 统计 'apple' 出现的次数
count_apple = fruits.count('apple')
print(f"'apple' 在元组中出现的次数: {count_apple}")
fruits.count('apple')
:统计元组fruits
中元素'apple'
出现的次数,结果为3
。
字符串也是一种序列类型,count
方法可以用来统计字符串中某个子字符串出现的次数。
sentence = "Hello, hello, world!"
# 统计 'hello' 出现的次数(区分大小写)
count_hello = sentence.count('hello')
print(f"'hello' 在字符串中出现的次数: {count_hello}")
# 统计 'o' 出现的次数
count_o = sentence.count('o')
print(f"'o' 在字符串中出现的次数: {count_o}")
sentence.count('hello')
:统计字符串sentence
中子字符串'hello'
出现的次数,结果为1
。注意,该方法区分大小写,所以'Hello'
不会被统计在内。sentence.count('o')
:统计字符串sentence
中字符'o'
出现的次数,结果为2
。
enumerate获取列表中每个元素值与索引值
alist = ['a', 'b', 'c']
for index, item in enumerate(alist):
print(index, item)
最终执行结果:
0 a
1 b
2 c
enumerate(arr):可以获取列表中每个元素的索引 和它对应的值。
zip
zip()
是一个内置函数,其主要功能是将多个可迭代对象(像列表、元组、字符串等)中的对应元素打包成一个个元组,接着返回由这些元组构成的迭代器。
基本用法
zip()
函数的基本语法为zip(*iterables)
,其中*iterables
表示接收任意数量的可迭代对象作为参数。
# 定义两个列表
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
# 使用 zip 函数将两个列表打包
zipped = zip(list1, list2)
# 将 zip 对象转换为列表,方便查看结果
result = list(zipped)
print(result)
在上述代码中,zip(list1, list2)
会把list1
和list2
中对应位置的元素打包成元组,最终得到[(1, 'a'), (2, 'b'), (3, 'c')]
。
处理不同长度的可迭代对象
当传入的可迭代对象长度不一致时,zip()
函数会以最短的可迭代对象为基准进行打包,多余的元素会被忽略。示例如下:
list1 = [1, 2, 3]
list2 = ['a', 'b']
zipped = zip(list1, list2)
result = list(zipped)
print(result)
list2
的长度比list1
短,因此zip()
函数只会打包前两个元素,结果为[(1, 'a'), (2, 'b')]
。
与*
运算符结合使用
zip()
函数可以和*
运算符配合使用来实现解包操作。
zipped = [(1, 'a'), (2, 'b'), (3, 'c')]
# 使用 * 运算符解包
unzipped = zip(*zipped)
# 将解包后的结果转换为列表
list1, list2 = map(list, unzipped)
print(list1)
print(list2)
在上述代码中,zip(*zipped)
对zipped
进行解包操作,将元组中的元素重新拆分成两个列表,最终输出[1, 2, 3]
和['a', 'b', 'c']
。
map
map()
是一个内置函数,它的主要作用是将一个函数应用到可迭代对象(如列表、元组等)的每个元素上,并返回一个包含结果的迭代器。
map(function, iterable, ...)
function
:这是一个需要应用到可迭代对象每个元素上的函数。iterable
:表示一个或多个可迭代对象,map()
会依次将function
应用到这些可迭代对象的对应元素上。如果提供了多个可迭代对象,function
必须能够接受与可迭代对象数量相同的参数。
使用map()
函数将列表中的每个元素都乘以 2:
# 定义一个函数,用于将输入的数字乘以 2
def multiply_by_two(x):
return x * 2
# 定义一个列表
numbers = [1, 2, 3, 4, 5]
# 使用 map() 函数将 multiply_by_two 应用到 numbers 列表的每个元素上
result = map(multiply_by_two, numbers)
# 将 map 对象转换为列表,方便查看结果
result_list = list(result)
print(result_list)
将multiply_by_two
函数应用到numbers
列表的每个元素上,最终返回一个包含结果的迭代器,将迭代器转化为list后打印,得到[2, 4, 6, 8, 10]
处理多个可迭代对象
# 定义一个函数,用于将两个数字相加
def add(x, y):
return x + y
# 定义两个列表
list1 = [1, 2, 3]
list2 = [4, 5, 6]
# 使用 map() 函数将 add 函数应用到两个列表的对应元素上
result = map(add, list1, list2)
# 将 map 对象转换为列表
result_list = list(result)
print(result_list)
将add
函数应用到list1
和list2
对应位置的元素上,这里是让让list1[0]+list2[0],list1[1]+list2[1]等,最终得到[5, 7, 9]
。
与lambda
函数结合使用
通常,我们会使用lambda
函数(匿名函数)与map()
函数结合。
# 使用 lambda 函数和 map() 实现元素乘以 2
numbers = [1, 2, 3, 4, 5]
result = map(lambda x: x * 2, numbers)
print(list(result))
# 使用 lambda 函数和 map() 实现两个列表对应元素相加
list1 = [1, 2, 3]
list2 = [4, 5, 6]
result = map(lambda x, y: x + y, list1, list2)
print(list(result))
map()
返回的是一个迭代器,通常需要将其转换为列表或其他序列类型才能查看完整的结果。
sorted排序
sorted()
是一个内置函数,对可迭代对象(像列表、元组、字符串等)进行排序操作,并且会返回一个新的已排序列表,而不会改变原始的可迭代对象。
sorted(iterable, key=None, reverse=False)
iterable
:这是必须提供的参数,代表要进行排序的可迭代对象,比如列表、元组、字符串等。key
:这是一个可选参数,是一个函数,它会作用于可迭代对象中的每个元素,排序时会依据这个函数的返回值来进行。默认值为None
,表示直接比较元素本身。reverse
:同样是可选参数,是一个布尔值。若设置为True
,则进行降序排序;若为False
(默认值),则进行升序排序。
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
sorted_numbers = sorted(numbers)
print(sorted_numbers)
sorted(numbers)
会对numbers
列表进行升序排序,最终输出[1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
。
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
sorted_numbers = sorted(numbers, reverse=True)
print(sorted_numbers)
对numbers
列表进行降序排序,输出结果为[9, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1]
。
words = ["apple", "banana", "cherry", "date"]
# 按照字符串的长度进行排序
sorted_words = sorted(words, key=len)
print(sorted_words)
key=len
表示按照字符串的长度来排序,最终输出['date', 'apple', 'banana', 'cherry']
。
students = [("Alice", 25), ("Bob", 20), ("Charlie", 22)]
# 按照年龄进行排序
sorted_students = sorted(students, key=lambda x: x[1])
print(sorted_students)
lambda
函数lambda x: x[1]
作为key
参数,意味着按照元组的第二个元素(年龄)进行排序,输出结果为[('Bob', 20), ('Charlie', 22), ('Alice', 25)]
。
也可以对 可迭代对象(列表、元组)进行排序:
data = [(1, 3), (2, 2), (1, 2)]
sortedList = sorted(data)
print(sortedList)
当使用sorted()
函数对可迭代对象进行排序,且不传入key
参数时,默认会按照元素的字典序进行排序。
对于由zip
函数创建的可迭代对象,其元素是元组。在这种情况下,排序首先比较元组的第一个元素,如果相等,则继续比较第二个元素,以此类推,直到找到不同的元素或到达元组的末尾。
上述代码中,sorted
函数会先比较每个元组的第一个元素,所以(1, 3)
和(1, 2)
会排在(2, 2)
前面,而对于(1, 3)
和(1, 2)
,由于第一个元素相同,会继续比较第二个元素,从而确定它们的顺序。最终结果为[(1, 2), (1, 3), (2, 2)]
。
当然也可以使用匿名函数进行指定,按照指定的位数进行排序:
data = [(1, 3), (2, 2), (1, 2)]
# 按照元组的第一个元素升序排序
sorted_by_first = sorted(data, key=lambda x: x[0])
print(sorted_by_first)
# 按照元组的第二个元素升序排序
sorted_by_second = sorted(data, key=lambda x: x[1])
print(sorted_by_second)
上述代码中,sorted(data, key=lambda x: x[0])
表示按照元组的第一个元素进行升序排序,sorted(data, key=lambda x: x[1])
表示按照元组的第二个元素进行升序排序。key
参数是一个函数,它指定了排序的依据。
sum求总和
sum()
是一个内置函数,主要用于计算可迭代对象(像列表、元组、集合等)中所有元素的总和。
sum(iterable, start=0)
iterable
:这是一个可迭代对象,比如列表、元组、集合等,其元素应当是可以进行加法运算的类型,像数字类型。start
:这是一个可选参数,代表求和的起始值,默认值为 0。
# 对列表中的数字求和
numbers = [1, 2, 3, 4, 5]
result = sum(numbers)
print(result)
# 指定起始值求和
numbers = [1, 2, 3, 4, 5]
result = sum(numbers, 10)
print(result)
将起始值设定为 10,所以sum()
函数会先把列表中的元素相加得到 15,再加上起始值 10,最终输出结果 25。
注意:
iterable
里的元素必须是能够进行加法运算的类型,不然会引发类型错误。- 对于浮点数求和,要留意浮点数精度问题。要是需要高精度计算,可以考虑使用
decimal
模块。
标准库数组工具
groupby
from itertools import groupby
py标准库中模块itertools的函数groupby,可将迭代器中相邻的重复元素分组。来看一个简单的例子:
from itertools import groupby
# 定义一个已排序的列表
data = [1, 1, 2, 2, 2, 3, 4, 4]
# 使用 groupby 进行分组
for key, group in groupby(data):
print(f"Key: {key}, Group: {list(group)}")
运行结果:
Key: 1, Group: [1, 1]
Key: 2, Group: [2, 2, 2]
Key: 3, Group: [3]
Key: 4, Group: [4, 4]
groupby
函数把相邻的相同元素划分成组,key
代表每组的元素值,group
是一个迭代器,它包含了该组的所有元素。
再考虑一下,在使用 groupby 函数时,如何保留分组的索引信息?
from itertools import groupby
# 示例数据
data = ['a', 'a', 'b', 'b', 'b', 'c', 'd', 'd']
# 为每个元素添加索引
indexed_data = enumerate(data)
"""
print(list(indexed_data))
# 得到 [(0, 'a'), (1, 'a'), (2, 'b'), (3, 'b'), (4, 'b'), (5, 'c'), (6, 'd'), (7, 'd')]
"""
# 使用 groupby 进行分组
groups = []
for key, group in groupby(indexed_data, key=lambda x: x[1]):
"""
print(f"分组依据的值(key): {key}")
print(list(group))
分组依据的值(key): a
[(0, 'a'), (1, 'a')]
分组依据的值(key): b
[(2, 'b'), (3, 'b'), (4, 'b')]
分组依据的值(key): c
[(5, 'c')]
分组依据的值(key): d
[(6, 'd'), (7, 'd')]
"""
indices = [index for index, _ in group]
groups.append((key, indices))
# 输出分组结果及索引
for key, indices in groups:
print(f"Key: {key}, Indices: {indices}")
indexed_data =enumerate(data)
enumerate
是一个内置函数,其作用是为可迭代对象(像列表、元组、字符串等)添加索引。enumerate
函数会返回一个枚举对象,此对象包含了一系列的元组,每个元组由索引和对应的元素值构成。这个对象里的每个元组包含两个元素:第一个元素是索引(从 0 开始),第二个元素是data
列表中对应位置的元素。
转化为列表,最终打印出:[(0, 'a'), (1, 'a'), (2, 'b'), (3, 'b'), (4, 'b'), (5, 'c'), (6, 'd'), (7, 'd')]
groupby(indexed_data, key=lambda x: x[1])
1、key
for后面的key
:表示当前分组的依据,也就是lambda
函数针对分组内元素执行后所返回的值 这里是x:x[1],以第一个为例就是'a'。
在groupby
函数里,key=
参数的作用是指定一个回调函数(这里是指定匿名函数lambda),该函数会对数组中每个元素执行,然后依据处理结果进行分组,分组后的结果存入for .. group in .. 的group中。
2、lambda
lambda x: x[1]
是一个回调函数!,用于指定对indexed_data中的每个元素执行的函数!最终的结果赋予for key...的key!
lambda函数简介
lambda
函数也被称作匿名函数,它是一种小型的、临时的、一次性使用的函数。与常规函数不同,lambda
函数不需要使用def
关键字来定义,也没有函数名。其语法格式为:
lambda 参数列表: 表达式
lambda
函数会接收参数列表中的参数,然后对表达式进行求值,并返回表达式的结果。
对于lambda x: x[1]
x
代表indexed_data
中的每个元素。由于indexed_data
是通过enumerate(data)
得到的,所以x
是一个包含索引和元素值的元组,例如(0, 1)
、(1, 1)
等。x[1]
表示取元组x
的第二个元素,也等价于原始数据列表data
中的元素值。lambda x: x[1]
这个匿名函数的功能是接收一个元组x
,(这个x就是遍历的indexed_data中的每一个元素)然后返回该元组的第二个元素(比如 (0, ‘a’) 就返回'a'),因此每轮循环,将得到的比如'a',赋值给for后面的Key。然后groupby就会根据这个key (‘a’)进行分组,并将分组结果保存到for 后面的group中。
3、indices = [index for index, _ in group]
这里使用了列表推导式(list comprehension)。其目的是从group
这个迭代器中提取出所有元素的索引,然后将这些索引存储在一个新的列表indices
中。
这里我们挑一轮遍历得到的group,[(2, 'b'), (3, 'b'), (4, 'b')] 为例:
列表推导式简介
列表推导式是 Python 中一种简洁且高效的创建列表的方式,它允许你通过一种紧凑的语法来生成列表。其基本语法如下:
[表达式 for 变量 in 可迭代对象 if 条件]
其中,表达式
是对每个元素进行处理的表达式,变量
是从可迭代对象
中取出的元素,if 条件
是可选的过滤条件。
index, _
:这是元组解包(tuple unpacking)的操作。index
用于接收元组中的第一个元素,也就是索引;_
是 Python 中的一个惯用符号,通常用于表示一个临时变量,在这里它接收元组中的第二个元素(即值),但由于我们只关心索引,所以使用_
来忽略这个值。[index for index, _ in group]
:这是一个列表推导式,它会遍历group
中的每个元组,将元组中的索引提取出来(分别遍历 【index是2,_是'b'】【index是3,_是'b'】【index是4,_是'b'】 ),并将这些结果中的index(索引)依次添加到一个新的列表中。最终,这个新列表会被赋值给变量indices
。
indices = [index for index, _ in group]
print(f"得到的indices: {indices}")
遍历打印得到的结果:
得到的indices: [0, 1]
得到的indices: [2, 3, 4]
得到的indices: [5]
得到的indices: [6, 7]
如果是这样写:
indices = [_ for index, _ in group]
print(f"得到的indices: {indices}")
得到的indices: ['a', 'a']
得到的indices: ['b', 'b', 'b']
得到的indices: ['c']
得到的indices: ['d', 'd']
最终for key, indices in groups:该函数执行的结果:
Key: a, Indices: [0, 1]
Key: b, Indices: [2, 3, 4]
Key: c, Indices: [5]
Key: d, Indices: [6, 7]
实现了将每个值进行分组,并且获取了原本该值对应的索引。
tee
itertools.tee
是 Python 标准库itertools
模块中的一个函数,其主要作用是将一个迭代器复制成多个独立的迭代器,这些新生成的迭代器可以独立地对原迭代器的数据进行遍历,而不会相互干扰。
itertools.tee(iterable, n=2)
iterable
:这是必需的参数,代表要进行复制的原始迭代器或者可迭代对象,例如列表、元组、生成器等。n
:该参数是可选的,默认值为 2。它表示要生成的独立迭代器的数量。- 返回值:
tee
函数会返回一个包含n
个独立迭代器的元组。这些迭代器可以独立地对原迭代器的数据进行遍历。
比如我有下面这段代码:
sortedList = [(1, 5383), (1, 5566), (1, 5648),... (2, 8206), (3, 6437),.. (9, 28208),.. (11, 65458), (11, 65583)]
for x,y in groupby( sortedList, key=lambda _:_[0] ):
print('依据分组的x', x); # x是1...3..9..11
print('分组的结果y', list(y)) # y正常是 所有的[(1, 5383)] ,所有的 [(11, 65458), (11, 65583)] 这些,分别找出x是1..9..11的集合
# 仅仅获取 元组中 5383 5566... 65583 这样的 第二个值 组成一个列表
y_list = [v for _, v in y]
这里会发现,程序会报错,提示y不能用了。
这是因为在打印的时候,输出了 list(y),迭代器就会被耗尽,后续就无法再次使用它。若你想打印y
的副本,同时又不影响后续操作,可借助itertools.tee
函数创建迭代器的副本。
我们可以这样修改:
from itertools import groupby, tee
for x, y in groupby(sortedList, key=lambda _: _[0]):
# 创建迭代器的副本
y_copy, y_original = tee(y)
# x就是lambda匿名函数执行结果,返回 每个元组中的第一个值
print('依据分组的x', x)
# y就是根据返回的值,进行分组的结果
print('分组的结果y', list(y_copy))
y_list = [v for _, v in y_original]
tee
函数返回的是一个包含多个迭代器的元组,而非迭代器的副本内容。要打印副本内容,需要对tee
返回的迭代器进行解包,然后将其中一个迭代器转换为列表来打印。tee(要复制的迭代器,复制的数量=2【默认是2】),所以对结果要进行解包。
ycopy = tee(y) 错!!
y_copy, y_original = tee(y) 对
tee
函数使用:y_copy, y_original = tee(y)
把tee
返回的元组解包成两个迭代器,y_copy
用于打印,y_original
用于后续操作。
再看个单独使用的简单的例子:
from itertools import tee
# 创建一个简单的可迭代对象
numbers = [1, 2, 3, 4, 5]
# 使用 tee 函数复制迭代器,默认生成 2 个迭代器
iter1, iter2 = tee(numbers)
# 遍历第一个迭代器
print("第一个迭代器的元素:")
for num in iter1:
print(num)
# 遍历第二个迭代器
print("第二个迭代器的元素:")
for num in iter2:
print(num)
首先创建了一个列表numbers
。接着使用tee
函数将这个列表的迭代器复制成两个独立的迭代器iter1
和iter2
。之后分别对这两个迭代器进行遍历,会发现它们都能完整地输出原列表中的元素。
注意事项
- 内存消耗:由于
tee
函数需要使用缓冲队列来存储数据,若原迭代器的数据量非常大,或者生成的迭代器数量过多,可能会导致较高的内存消耗。 - 原迭代器的使用:一旦使用
tee
函数复制了迭代器,就不应该再直接使用原迭代器,否则可能会导致数据不一致的问题。 - 迭代器耗尽:每个新生成的迭代器都是独立的,当其中一个迭代器耗尽时,不会影响其他迭代器的使用。不过,所有迭代器共享同一个缓冲队列,若某个迭代器消耗数据的速度远快于其他迭代器,可能会导致缓冲队列占用大量内存。