Python自学指南-第三章-数据结构

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)对组合而成的数据结构。

字典中的每个键都与一个值相关联,其中

  1. 键,必须是可 hash 的值,如字符串,数值等
  2. 值,则可以是任意对象

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 中已经取消了,原因是有一种更简单直观的方法,那就是使用 innot 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. 生成器的激活

生成器对象,在创建后,并不会执行任何的代码逻辑。

想要从生成器对象中获取元素,那么第一步要触发其运行,在这里称之为激活。

方法有两种:

  1. 使用next() :上面已经讲过
  2. 使用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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

李乾星

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

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

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

打赏作者

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

抵扣说明:

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

余额充值