3.1 【基础】列表
列表(英文名 list),python的列表是一种有序且可变的序列,列表使用中括号[]进行定义,各项元素之间使用逗号分隔。python的列表与其他编程语言中的数组很像,但独特之处在于python并不要求列表中的元素必须都是同一个类型,而是可以存储任意类型的数据。
列表和字符串都是序列,同样支持索引方式访问,也支持切片操作,不同于字符串的是你可以修改列表的内容。
在定义列表时,不需要指定列表的长度,也不用担心持续向列表中写入新的数据会达到存储容量限制,python列表是动态扩容的。
这里面有两个重点:
- 元素:没有要求同一类型,所以可以是任意类型。
- 顺序:按顺序排列而成,说明列表是有序的。
1. 创建列表
创建列表有两种方法
第一种方法:使用[] 创建列表
empty_list = [] # 创建一个空列表
第二种方法:使用内置函数创建列表
除了使用[ ]
创建列表外,Python 还提供了一个内置的函数 list(),使用它可以将其它数据类型转换为列表类型。
lst1 = list("python")
lst2 = list([1, 2, 3])
print(lst1) # ['p', 'y', 't', 'h', 'o', 'n']
print(lst2) # [1, 2, 3]
#将字符串转换成列表
list1 = list("hello")
print(list1)
#将元组转换成列表
tuple1 = ('Python', 'Java', 'C++', 'JavaScript')
list2 = list(tuple1)
print(list2)
#将字典转换成列表
dict1 = {'a':100, 'b':42, 'c':9}
list3 = list(dict1)
print(list3)
#将区间转换成列表
range1 = range(1, 6)
list4 = list(range1)
print(list4)
#创建空列表
print(list())
2. 增删改查
增删改查:是 新增元素、删除元素、修改元素、查看元素的简写。
查找元素
使用 [i]
的方式查看第 i+1
个元素。例如 x 的起始值为 0 ,代表第一个元素。即使用下标
phones = ["Apple", "Huawei", "Xiaomi"]
print(phones[0])
print(phones[1])
print(phones[2])
运行结果
Apple
Huawei
Xiaomi
list.index()方法可以查询一个元素的下标;
phones = ["Apple", "Huawei", "Xiaomi", "Huawei"]
print(phones.index("Huawei"))
运行结果
1
使用list.count()方法可以查询指定元素在列表中出现的次数。
phones = ["Apple", "Huawei", "Xiaomi", "Huawei"]
print(phones.count("Huawei")
运行结果
2
使用内置函数len()
,可以查看该列表中有几个值
phones = ["Apple", "Huawei", "Xiaomi"]
print(len(phones))
运行结果
3
使用in语句可以查询一个元素是否存在列表中;
phones = ["Apple", "Huawei", "Xiaomi"]
if 'Apple' in phones:
print('yes')
else:
print('No')
运行结果
yes
新增元素
使用列表的 append 、insert、和 extend 方法
- append 方法:将元素插入在列表的最后一个位置
phones = []
print(phones)
phones.append("Apple")
print(phones)
phones.append("Huawei") # append 后 Huawei 会在最后一个位置
print(phones)
运行结果
[]
['Apple']
['Apple', 'Huawei']
- insert 方法:将元素插入在列表的指定的位置
phones = ["Apple", "Huawei", "Xiaomi"]
phones.insert(1, "OPPO") # 把 OPPO 插入到索引为 1 的位置
print(phones)
运行结果
['Apple', 'OPPO', 'Huawei', 'Xiaomi']
- extend:将一个新的列表直接连接在旧的列表后面
phones = ["Apple", "Huawei", "Xiaomi"]
new_phones = ["OPPO", "VIVO"]
phones.extend(new_phones)
print(phones)
运行结果
['Apple', 'Huawei', 'Xiaomi', 'OPPO', 'VIVO']
修改元素
直接使用 list[x]=new_item
的方法直接替换
>>> phones = ["Apple", "Huawei", "Xiaomi"]
>>> phones[1] = "OPPO"
>>> phones
['Apple', 'OPPO', 'Xiaomi']
使用切片修改多个元素的值
# 切片举例 nums = [40, 36, 89, 2, 36, 100, 7]
#
# #修改第 1~4 个元素的值(不包括第4个元素)
# nums[1: 4] = [45.25, -77, -52.5]
# print(nums)
删除元素
使用 pop ,remove 、clear 方法或者 del 语句删除元素
- pop 方法:删除指定位置的元素。默认删除最后一个元素,
phones = ["Apple", "Huawei", "Xiaomi"]
phones.pop() # 删除最后一个元素
phones.pop(0) # 删除索引为0的元素
print(phones)
运行结果
['Huawei']
ps:pop()方法可以将弹出的元素进行接收
phones = ["Apple", "Huawei", "Xiaomi"]
a = phones.pop() # 删除最后一个元素
b = phones.pop(0) # 删除索引为0的元素
print(a, b)
运行结果
Xiaomi Apple
- remove:删除第一个值为 x 的元素。
phones = ["Apple", "Huawei", "Xiaomi", "Huawei"]
phones.remove("Huawei")
print(phones)
运行结果
['Apple', 'Xiaomi', 'Huawei']
- clear 方法:把所有的元素清空
phones = ["Apple", "Huawei", "Xiaomi"]
phones.clear()
print(phones)
运行结果
[]
- del 语句:清空列表,还有另一种方法
phones = ["Apple", "Huawei", "Xiaomi"]
del phones[:]
phones
运行结果
[]
使用 del 语句,还可以删除某一个或者某几个连续的元素。
phones = ["Apple", "Huawei", "Xiaomi", "OPPO", "VIVO"]
del phones[0] # 删除索引为0的元素
print(phones)
del phones[1:3] # 删除索引在 [1:3) 区间内元素,注意是左闭右开区间
print(phones)
运行结果
['Huawei', 'Xiaomi', 'OPPO', 'VIVO']
['Huawei', 'VIVO']
3. 列表反转
列表反转有两种方法
第一种方法:使用自带的 reverse 方法
nums = [1,2,3,4,5]
nums.reverse()
print(nums)
运行结果
[5, 4, 3, 2, 1]
第二种方法:使用切片的方法
nums = [1,2,3,4,5]
print(nums[::-1])
运行结果
[5, 4, 3, 2, 1]
这两种方法,区别在于:
- reverse 方法是原地反转,作用在原对象上
- 切片反转是返回一个新对象,原对象不改变
4. 列表排序
列表的排序同样有两种方法:
第一种方法:列表对象内置了 sort 方法,可方便我们对元素进行排序。
alist = [4,8,1,7,2]
alist.sort()
print(alist)
运行结果
[1, 2, 4, 7, 8]
第二种方法:Python 有个内置的 sorted 函数,它不仅可用作列表的排序,后面我们还会学到 字典 等其他数据结构的排序也会用到它。
alist = [4,8,1,7,2]
print(sorted(alist))
运行结果
[1, 2, 4, 7, 8]
不管用哪种方法,都要保证列表内的元素俩俩是可比较的。
比如,数值和数值是可比较的,字符串和字符串之间是可比较的。
但是数值和字符串是不可比较的,示例如下
alist = [9, 3, 1, "d", "k", "a"]
print(alist.sort())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'
>>>
除了上面介绍的俩种之外,其实利用 sort 函数还可以实现自定义排序
3.2 【基础】元组
元组(英文名 tuple),和列表非常的相似,它也是由一系列元素按顺序进行排列而成的容器。
不同的是,元组是不可变的,而列表是可变的。
1. 创建元组
创建元组有三种方法
第一种方法:直接使用 圆括号 将所有的元素进行包围。这有别于创建列表时使用的是中括号:[]
atuple = (1,2,3,4)
print(atuple)
运行结果
(1, 2, 3, 4)
第二种方法:有时候,创建元组时,圆括号可有可无的。
btuple = 1,2,3,4
print(btuple)
运行结果
(1, 2, 3, 4)
第三种方法:使用元组推导式,由于元组是不可变的,所以生成一个生成器对象。
ctuple = (i for i in range(1,6))
print(ctuple)
运行结果
<generator object <genexpr> at 0x10a288f90>
上面三种方法介绍完毕~
你以为就这么简单?
当你在创建只有一个元素的元组时,你有可能会这样子创建
ctuple = (1)
print(type(ctuple))
print(ctuple)
运行结果
<class 'int'>
1
却发现,创建出来的并不是 tuple,而是一个 int 对象。
此时千万要记住,当你创建只包含一个元素的元组时,要在第一个元素后面加一个逗号
ctuple = (1,)
print(type(ctuple))
print(ctuple)
dtuple = 1,
print(type(dtuple))
print(dtuple)
运行结果
<class 'tuple'>
(1,)
<class 'tuple'>
(1,)
另外,创建空元组可以这样
a = tuple() # 第一种方法
print(a)
print(type(a))
b = () # 第二种方法
print(b)
print(type(b))
运行结果
()
<class 'tuple'>
()
<class 'tuple'>
2. 增删改查
最前面我们说过,元组是不可变的。因此,你想对元组进行修改的行为都是不被允许的。
呐,看一下示例,查看元素可以,但是修改元素和删除元素都报错了。
# 创建一个元组
t = (1, 2, 3, 4, 5)
# 查看元素
print(t[0]) # 输出:1
print(t[3]) # 输出:4
# 修改元素
try:
t[0] = 10
except TypeError as e:
print(f"修改元素时发生了异常:{e}")
# 输出:修改元素时发生了异常:'tuple' object does not support item assignment
# 删除元素
try:
del t[1]
except TypeError as e:
print(f"删除元素时发生了异常:{e}")
# 输出:删除元素时发生了异常:'tuple' object doesn't support item deletion
在上述代码中,我们首先创建了一个元组 t
,其中包含了 5 个整数。我们通过下标来查看元素,即使用 t[index]
的方式获取指定位置的元素。由于元组是不可变的,因此在尝试修改元素或删除元素时,Python 都会抛出 TypeError
异常,并提示元组不支持这些操作。为了捕获这些异常,我们使用了 try...except...
结构,并在发生异常时输出错误信息。
新增元素呢?当然同样也是不支持的
由于元组的长度是固定的,因此也不能向元组中添加新的元素。
下面是一个示例,演示了向元组中添加新元素的行为,该操作会引发 AttributeError
异常,因为元组对象没有 append
方法和其他添加元素的方法:
# 创建一个元组
t = (1, 2, 3)
# 向元组中添加新元素
try:
t.append(4)
except AttributeError as e:
print(f"添加元素时发生了异常:{e}")
# 输出:添加元素时发生了异常:'tuple' object has no attribute 'append'
在上面的代码中,我们创建了一个包含 3 个整数的元组 t
,然后尝试向其中添加新元素 4
,这里我们使用了 append
方法,但是 Python 解释器抛出了 AttributeError
异常,并提示元组对象没有 append
方法。
因此,我们不能像列表一样通过添加元素来修改元组,只能通过重新创建一个新的元组,然后将原来的元组替换成新的元组来达到类似的效果。
3. 元组与列表的转换
虽然元组可能看起来与列表很像,但它们通常是在不同的场景被使用,并且有着不同的用途。
元组是 immutable (不可变的),其序列通常包含不同种类的元素,并且通过解包或者索引来访问(如果是 namedtuples
的话甚至还可以通过属性访问)。
列表是 mutable (可变的),并且列表中的元素一般是同种类型的,并且通过迭代访问。
那有办法可以实现二者的转换吗?
当然有,而且非常简单。
将元组转成列表
atuple = (1,2,3,4)
print(type(atuple))
print(list(atuple))
运行结果
[1, 2, 3, 4]
将列表转成元组
alist = [1,2,3,4]
print(type(alist))
print(tuple(alist))
运行结果
(1, 2, 3, 4)
3.3 【基础】字典
字典(英文名 dict),它是由一系列的键值(key-value)对组合而成的数据结构。
字典中的每个键都与一个值相关联,其中
- 键,必须是可 hash 的值,如字符串,数值等
- 值,则可以是任意对象
1. 创建字典
创建一个字典有三种方法
第一种方法:先使用 dict()
创建空字典实例,再往实例中添加元素
profile = dict(name="航海", age=27, )
print(profile)
运行结果
{'name': '航海', 'age': 27}
第二种方法:直接使用 {}
定义字典,并填充元素。
profile = {"name": "航海", "age": 27}
print(profile)
运行结果
{'name': '航海', 'age': 27}
第三种方法:使用 dict()
构造函数可以直接从键值对序列里创建字典。
info = [('name', '航海 '), ('age', 27)]
print(dict(info))
运行结果
{'name': '航海 ', 'age': 27}
第四种方法:使用字典推导式
adict = {x: x**2 for x in (2, 4, 6)}
print(adict)
运行结果
{2: 4, 4: 16, 6: 36}
2. 增删改查
增删改查:是 新增元素、删除元素、修改元素、查看元素的简写。
由于,内容比较简单,让我们直接看演示
查看元素
查看或者访问元素,直接使用 dict[key]
的方式就可以
profile = {"name": "航海", "age": 27, "主题": "编程练习"}
print(profile["主题"])
运行结果
'编程练习'
但这种方法,在 key 不存在时会报 KeyValue 的异常
profile = {"name": "航海", "age": 27}
print(profile["gender"])
运行结果
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'gender'
所以更好的查看获取值的方法是使用 get()
函数,当不存在 gender 的key时,默认返回 male
profile = {"name": "航海", "age": 27, "主题": "编程练习"}
print(profile.get("gender", "male"))
运行结果
'male'
新增元素
新增元素,直接使用 dict[key] = value
就可以
profile = dict()
print(profile)
profile["name"] = "航海"
profile["age"] = 27
profile["主题"] = "编程练习"
print(profile)
运行结果
{'name': '航海','age': 27,'主题': '编程练习'}
修改元素
修改元素,直接使用 dict[key] = new_value
就可以
profile = {"name": "航海", "age": 27, "主题": "编程练习"}
profile["age"] = 28
print(profile)
运行结果
{'name': '航海', 'age': 28, '主题': '编程练习'}
删除元素
删除元素,有三种方法
第一种方法:使用 pop 函数
profile = {"name": "航海", "age": 27, "主题": "编程练习"}
print(profile.pop("age"))
print(profile)
运行结果
{'name': '航海', '主题': '编程练习'}
第二种方法:使用 del 函数
profile = {"name": "航海", "age": 27, "主题": "编程练习"}
del profile["age"]
print(profile)
运行结果
{'name': '航海', '主题': '编程练习'}
3. 重要方法
判断key是否存在
在 Python 2 中的字典对象有一个 has_key 函数,可以用来判断一个 key 是否在该字典中
profile = {"name": "航海", "age": 27, "主题": "编程练习"}
print(profile.has_key("name"))
print(profile.has_key("gender"))
运行结果
True
True
但是这个方法在 Python 3 中已经取消了,原因是有一种更简单直观的方法,那就是使用 in
和 not in
来判断。
profile = {"name": "航海", "age": 27, "主题": "编程练习"}
print("name" in profile)
print("gender" in profile)
运行结果
True
False
设置默认值
要给某个 key 设置默认值,最简单的方法
profile = {"name": "航海", "age": 27, "主题": "编程练习"}
if "gender" not in profile:
profile["gender"] = "male"
实际上有个更简单的方法
profile = {"name": "航海", "age": 27, "主题": "编程练习"}
profile.setdefault("gender", "male")
3.4 【基础】集合
其实,Python 中有两种集合类型,一种是 set 类型的集合,另一种是 frozenset 类型的集合,它们唯一的区别是,set 类型集合可以做添加、删除元素的操作,而 forzenset 类型集合不行。
本节先介绍 set 类型集合,后续章节再介绍 forzenset 类型集合。
1. python集合定义
python的集合是一个无序且没有重复元素的序列,集合中的元素必须是可hash对象。集合不记录元素位置和插入顺序,因此,也不支持索引,切片等其他序列类的操作。
集合的创建有两种方法
第一种方法:使用 花括号 {}
直接创建
language_set = {'java', 'c', 'python'} # 定义集合
print(language_set)
运行结果
{'c', 'java', 'python'}
第二种方法:使用 set()
方法进行创建,当set()
函数不接任何参数时,创建的是空集合,如果不创建空集合,可以传入一个列表。
bset = set() # 空集合 定义空集合不能使用{} ,因为{ } 是创建空字典的方法,定义空集合可以使用set()。
bset=set([])
cset = set(["Apple", "Huawei", "Xiaomi"])
print(cset)
print(type(cset))
运行结果
{'Apple', 'Huawei', 'Xiaomi'}
<class 'set'>
2. 增删改查
增加元素
使用 add
函数可以往集合中传入函数
language_set = {'java', 'c', 'python'} # 定义集合
language_set.add('c++')
print(language_set)
运行结果
{'java', 'c', 'python', 'c++'}
另外,还可以使用 update
函数,来往集合中添加元素,update
函数后可接集合,列表,元组,字典等。
这是接集合的例子
aset = set()
print(aset)
aset.update({"Apple"}) # 接集合
print(aset)
aset.update(["Huawei"]) # 接列表
print(aset)
aset.update(("Xiaomi",)) # 接元组
print(aset)
aset.update({"VIVO": "xxxx"}) # 接字典
print(aset)
运行结果
set()
{'Apple'}
{'Apple', 'Huawei'}
{'Xiaomi', 'Apple', 'Huawei'}
{'Xiaomi', 'VIVO', 'Apple', 'Huawei'}
删除元素
使用 remove
函数可以删除集合中的元素
aset = {"Apple", "Huawei", "Xiaomi"}
aset.remove("Xiaomi")
print(aset)
运行结果
{'Apple', 'Huawei'}
使用 remove()
方法尝试从集合中删除一个不存在的元素 “VIVO”。由于 “VIVO” 并不存在于集合中,因此会触发 KeyError
异常。
aset = {"Apple", "Huawei", "Xiaomi"}
try:
aset.remove("VIVO")
except KeyError as e:
print(f"删除元素失败: {e}")
print(aset)
运行结果
删除元素失败: 'VIVO'
{'Huawei', 'Xiaomi', 'Apple'}
可以看到输出结果为 删除元素失败: 'VIVO'
和 {'Huawei', 'Apple', 'Xiaomi'}
。其中,捕获了 KeyError
异常并打印了错误信息,防止程序因为异常而崩溃。最后,输出了删除元素失败后的集合 aset
,仍然包含原来的三个元素。
对于这种情况,你可以使用 discard
函数,存在元素则移除,不存在也不会报错。
aset = {"Apple", "Huawei", "Xiaomi"}
aset.discard("VIVO")
print(aset)
运行结果
{'Apple', 'Huawei', 'Xiaomi'}
此外,还有一个 pop
函数,用于从集合中随机删除元素,和列表、字典的 pop
不一样,这里的 pop 不能加任何的参数。
aset = {"Apple", "Huawei", "Xiaomi"}
print(aset.pop())
print(aset.pop())
print(aset.pop())
运行结果
最后,还要介绍一个 clear
函数,它用于清空集合的元素。
>>> aset = {"Apple", "Huawei", "Xiaomi"}
>>> aset
set(['Huawei', 'Xiaomi', 'Apple'])
>>> aset.clear()
>>> aset
set([])
运行结果
Apple
Huawei
Xiaomi
修改元素
文章开头处,已经说明了集合是 无序
的,因此集合是没有索引的。
既然没有索引,修改也无从谈起。
记住:集合只有添加元素、删除元素。
查询元素
同上,没有顺序,也就没有索引,没有索引,查询也无从谈起。
但是我们可以查看集合的其他内容
比如,查看集合的长度
aset = {"Apple", "Huawei", "Xiaomi"}
print(len(aset))
运行结果
3
3. 集合运算
求合集
将两个集合进行合并并去重,可以使用 union
函数,下面的示例中,由于 Huawei
是重复的元素,只会保留一个。
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
result = aset.union(bset)
print(result)
运行结果
{'Apple', 'Xiaomi', 'Huawei'}
另外还可以使用 |
的操作符
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
print(aset | bset)
运行结果
{'Huawei', 'Apple', 'Xiaomi'}
求差集
要找出存在集合 A 但是不存在 集合 B 的元素,就是对两个集合求差集。
可以使用 difference
函数,下面的示例中, Apple
在 aset 中存在,但在 bset 中不存在。
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
print(aset.difference(bset))
运行结果
{'Apple'}
另外还可以使用 -
的操作符,更加直观
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
print(aset - bset)
运行结果
{'Apple'}
求交集
要找出存在集合 A 并且存在集合 B 的元素,就是对两个集合求交集。
可以使用 intersection
函数
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
cset = aset.intersection(bset)
print(cset)
运行结果
{'Huawei'}
和 intersection
相似的还有一个 intersection_update
函数,它们的区别是,intersection_update
会原地更新在 aset 上,而不是会回交集。
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
aset.intersection_update(bset)
print(aset)
运行结果
{'Huawei'}
另外还可以使用 &
的操作符
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
result = aset & bset
print(result)
运行结果
{'Huawei'}
求不重合集
如果计算两个集合中不重复的元素集合,可以使用 symmetric_difference
函数
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
result = aset.symmetric_difference(bset)
print(result)
运行结果
{'Apple', 'Xiaomi'}
和 symmetric_difference
相似的还有一个 symmetric_difference_update
函数,它们的区别是,symmetric_difference_update
会原地更新在 aset 上,而不是直接返回。
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
aset.symmetric_difference_update(bset)
print(aset)
运行结果
{'Xiaomi', 'Apple'}
4. 集合判断
判断是否有某元素
aset = {"Apple", "Huawei"}
print("Apple" in aset)
运行结果
True
判断两集合是否有相同元素
如果两集合有相同元素,则返回 False,如果没有相同元素,则返回 True
aset = {"Apple", "Huawei"}
bset = {"Xiaomi", "Huawei"}
print(aset.isdisjoint(bset))
运行结果
False
判断是否是子集
aset = {"Apple", "Huawei"}
bset = {"Huawei"}
bset.issubset(aset)
运行结果
True
3.5 【基础】迭代器
1. 可迭代对象
可以利用 for 循环的对象,都叫可迭代对象。
譬如我们前面学过的 列表、元组、字典、字符串等都是可迭代对象。
alist = [0, 1, 2, 3, 4, 5]
for i in alist:
print(i)
运行结果
0
1
2
3
4
5
2. 是否可迭代?
对 Python 比较熟悉的朋友,肯定知道哪些数据类型是可迭代的,哪些是不可迭代的。
但是对新手来说,可能需要借助一些函数来判别,比如 Python 内置的 collections.abc
模块,这个模块只有在 Python 中才有噢,在这个模块中提供了一个 Iterable 类,可以用 isinstance 来判断。
from collections.abc import Iterable
print(isinstance([0, 1, 2], Iterable)) # 列表
print(isinstance({"name": "航海"}, Iterable)) # 字典
print(isinstance((1, 2, 3), Iterable)) # 元组
print(isinstance("hello", Iterable)) # 字符串
运行结果
True
True
True
True
但是这种方法并不是百分百准确(具体下面会说到),最准确的方法,还是应该使用 for 循环。
3. 可迭代协议
可迭代对象内部是如何实现在你对其进行 for 循环时,可以一个一个元素的返回出来呢?
这就要谈到迭代器协议。
第一种场景:如果一个对象内部实现了 __iter__()
方法 ,并返回一个迭代器实例,那么该对象就是可迭代对象
class Array:
mylist = [0,1,2]
# 返回迭代器类的实例
def __iter__(self):
return iter(self.mylist)
# 得到可迭代对象
my_list = Array()
print(isinstance(my_list, Iterable)) # True
for i in my_list:
print(i)
第二种场景:假设一个对象没有实现 __iter__()
,Python 解释器 __getitem__()
方法获取元素,如果可行,那么该对象也是一个可迭代对象。
from collections.abc import Iterable
class Array:
mylist = [0,1,2]
def __getitem__(self, item):
return self.mylist[item]
# 得到一个可迭代对象
my_list = Array()
print(isinstance(my_list, Iterable)) # False
for i in my_list:
print(i)
此时如果你使用 isinstance(my_list, Iterable)
去判断是否是可迭代,就会返回 False,因为 isinstance 这种方法就是检查对象是否有 __iter__
方法。这也论证了使用 isinstance(my_list, Iterable)
去判断是否可迭代是不准确的。
4. 什么是迭代器
当你对一个可迭代对象使用 iter 函数后,它会返回一个迭代器对象,对于迭代器对象,我们可以使用 next 函数,去获取元素,每执行一次,获取一次,等到全部获取完毕,会抛出 StopIteration 提示无元素可取。
alist = [0, 1, 2, 3]
gen = iter(alist)
print(gen)
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
输出结果
<list_iterator object at 0x7fca945b8d10>
0
1
2
3
Traceback (most recent call last):
File "/path/to/your/code.py", line 7, in <module>
print(next(gen))
StopIteration
5. 迭代器协议
对比可迭代对象,迭代器
的内部只是多了一个函数而已 – __next__()
正因为有了它,我们才可以用 next 来获取元素。
迭代器,是在可迭代的基础上实现的。要创建一个迭代器,我们首先,得有一个可迭代对象。 现在就来看看,如何创建一个可迭代对象,并以可迭代对象为基础创建一个迭代器。
from collections.abc import Iterator
class Array:
index = 0
mylist = [0,1,2]
# 返回该对象的迭代器类的实例
# 因为自己就是迭代器,所以返回self
def __iter__(self):
return self
# 当无元素时,必要抛出 StopIteration
def __next__(self):
if self.index <= len(self.mylist)-1:
value = self.mylist[self.index]
self.index += 1
return value
raise StopIteration
my_iterator = iter(Array())
print(isinstance(my_iterator, Iterator)) # output: True
print(next(my_iterator)) # output: 0
print(next(my_iterator)) # output: 1
print(next(my_iterator)) # output: 2
print(next(my_iterator)) # StopIteration
3.6 【基础】生成器
1. 什么是生成器?
生成器(英文名 Generator ),是一个可以像迭代器那样使用for循环来获取元素的函数。
生成器的出现(Python 2.2 +),实现了延时计算,从而缓解了在大量数据下内存消耗过猛的问题。
当你在 Python Shell 中敲入一个生成器对象,会直接输出 generator object
提示你这是一个生成器对象
gen = (i for i in range(5))
print(gen)
运行结果
<generator object <genexpr> at 0x10cae50b0>
2. 如何创建生成器?
使用列表推导式
在上面已经演示过,正常我们使用列表推导式时是下面这样子,使用 []
,此时生成的是列表。
mylist = [i for i in range(5)]
print(mylist)
运行结果
[0, 1, 2, 3, 4]
而当你把 []
换成 ()
,返回的就不是列表了,而是一个生成器
gen = (i for i in range(5))
print(gen)
for i in gen:
print(i)
运行结果
<generator object <genexpr> at 0x7f83ebd662d0>
0
1
2
3
4
使用 yield
yield
是什么东西呢? 它相当于我们函数里的 return,但与 return 又有所不同。
- 当一个函数运行到 yield 后,函数的运行会暂停,并且会把 yield 后的值返回出去。
- 若 yield 没有接任何值,则返回 None
- yield 虽然返回了,但是函数并没有结束
请看如下代码,我定义了一个 generator_factory
函数,当我执行 gen = generator_factory()
时,gen 就是一个生成器对象
def generator_factory(top=5):
index = 0
while index < top:
print("index 值为: " + str(index))
index = index + 1
yield index
raise StopIteration
gen = generator_factory()
print(gen)
for i in gen:
print(i)
运行结果
<generator object generator_factory at 0x7f83ebd66950>
index 值为: 0
1
index 值为: 1
2
index 值为: 2
3
index 值为: 3
4
index 值为: 4
5
3. 生成器的使用
从一个生成器对象中取出元素,和我们前面学过的通过切片访问列表中的元素不一样,它没有那么直观。
想要从生成器对象中取出元素,只有两种方法:
第一种方法:使用 next 方法一个一个地把元素取出来,如果元素全部取完了,生成器会抛出 StopIteration
的异常。
gen = (x for x in range(3))
print(gen)
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
运行结果
<generator object <genexpr> at 0x7f0eb31d5290>
0
1
2
Traceback (most recent call last):
File "/Users/username/Documents/test.py", line 7, in <module>
print(next(gen))
StopIteration
第二种方法:使用 for 循环一个一个地迭代出来
gen = (x for x in range(3))
for i in gen:
print(i)
运行结果
0
1
2
4. 生成器的激活
生成器对象,在创建后,并不会执行任何的代码逻辑。
想要从生成器对象中获取元素,那么第一步要触发其运行,在这里称之为激活。
方法有两种:
- 使用
next()
:上面已经讲过 - 使用
generator.send(None)
还以下面这段代码为例,可以看到 gen.send(None)
相当于执行了 next(gen)
def generator_factory(top=5):
index = 0
while index < top:
print("index 值为: " + str(index))
index = index + 1
yield index
raise StopIteration
gen = generator_factory()
print(gen.send(None))
print(gen.send(None))
运行结果
index 值为: 0
1
index 值为: 1
2
5. 生成器的状态
生成器在其生命周期中,会有如下四个状态
GEN_CREATED
# 生成器已创建,还未被激活GEN_RUNNING
# 解释器正在执行(只有在多线程应用中才能看到这个状态)GEN_SUSPENDED
# 在 yield 表达式处暂停GEN_CLOSED
# 生成器执行结束
通过下面的示例可以很轻松地理解这一过程(GEN_RUNNING
这个状态只有在多线程中才能观察到,这里就不演示啦)
def my_generator():
print("Starting generator...")
yield 1
print("Generator has yielded 1...")
yield 2
print("Generator has yielded 2...")
yield 3
print("Generator has yielded 3...")
print("Generator is now closing...")
gen = my_generator()
print("Generator created, state:", getgeneratorstate(gen))
next(gen)
print("Generator state:", getgeneratorstate(gen))
next(gen)
print("Generator state:", getgeneratorstate(gen))
next(gen)
print("Generator state:", getgeneratorstate(gen))
try:
next(gen)
except StopIteration:
pass
print("Generator state:", getgeneratorstate(gen))
运行结果
Generator created, state: GEN_CREATED
Starting generator...
Generator has yielded 1...
Generator state: GEN_SUSPENDED
Generator has yielded 2...
Generator state: GEN_SUSPENDED
Generator has yielded 3...
Generator is now closing...
Generator state: GEN_CLOSED
在这个示例中,我们定义了一个简单的生成器函数 my_generator()
,它在每个 yield 语句处暂停,并输出一些消息。我们首先创建了一个生成器对象 gen
,此时它的状态是 GEN_CREATED
。当我们第一次调用 next(gen)
时,生成器启动并执行,然后在第一个 yield 处暂停,状态变为 GEN_SUSPENDED
。每次调用 next(gen)
,生成器都会在 yield 处恢复执行,直到遇到最后一个 yield 语句。当最后一个值被 yield 出来后,生成器退出并关闭,状态变为 GEN_CLOSED
。
6. 生成器的异常
在最前面,我有定义了一个生成器函数。
def generator_factory(top=2):
index = 0
while index < top:
index = index + 1
yield index
raise StopIteration
在没有元素可返回时,我最后抛出了 StopIteration
异常,这是为了满足生成器的协议。
实际上,如果你不手动抛出 StopIteration
,在生成器遇到函数 return 时,会我自动抛出 StopIteration
。
请看下面代码,我将 raise StopIteration
去掉后,仍然会抛出异常。
def generator_factory(top=2):
index = 0
while index < top:
index = index + 1
yield index
gen = generator_factory()
print(next(gen))
print(next(gen))
print(next(gen))
运行结果
1
2
Traceback (most recent call last):
File "/path/to/your/file.py", line 8, in <module>
print(next(gen))
StopIteration