5.5 Python 迭代器与生成器

作者 : admin 本文共9949个字,预计阅读时间需要25分钟 发布时间: 2024-06-13 共1人阅读

文章目录

      • 1. 三元表达式
        • 1.1 格式
        • 1.2 示例
        • 1.3 嵌套
      • 2. 生成式
        • 2.1 列表生成式
        • 2.2 字典生成式
        • 2.3 集合生成式
        • 2.4 元组生成式
      • 3. 可迭代对象
      • 4. 迭代器
        • 4.1 迭代器的优缺点
        • 4.2 迭代器的惰性机制
        • 4.3 生成迭代器
        • 4.4 文本IO包装器
        • 4.5 字符串迭代器
        • 4.6 列表迭代器
        • 4.7 字典键迭代器
        • 4.8 元组迭代器
        • 4.9 集合迭代器
        • 4.10 注意事项
        • 4.11 模拟for循环
          • 1. 执行步骤
          • 2. 异常捕获
          • 3. while循环遍历迭代器对象
          • 4. 递归遍历迭代器对象
      • 5. 生成器
        • 5.1 生成器生成式
        • 5.2 生成器的特点
        • 5.3 yield关键字
          • 1. 返回生成器
          • 2. 迭代取值
          • 3. yield传参
        • 5.4 实例
      • 6. 练习

1. 三元表达式

1.1 格式
当需求为二选一的情况下推荐使用三元表达式.
格式: 条件成立采用的值 if 条件 else 条件不成立采用的值.
值不能是关键字, ... 属于特殊的符号, 不是关键字.
1.2 示例
a = 0
b = 1
print(a if a > b else b)

def func1():
    print('哈哈哈')
    
while True:
    # 当输入不'q时' 执行func1, 否则什么都不做
    func1() if input('输入q退出否则继续>>>:').strip() == 'q' else ...
1.3 嵌套
三元表达式可以嵌套使用, 尽量不要去使用, Python语句注重可读性, 嵌套太复杂了.
a = 0
b = 1
c = 2
# 从左往右开始执行
print(a if a > b else (c if c > b else a))

2. 生成式

生成式(comprehensions), 也称推导式, 作用是为容器类型快速生成元素.
生成式中for与if一起使用, 则不能搭配else, 应为else从句能跟for也能跟if这会会出现矛盾.
2.1 列表生成式
列表生成式语法: [满足条件执行的表达式 for 迭代变量 in 容器对象]
# 先执行for遍历将容器中的值赋给i, 在列表其它位置就可以使用i, 这里i仅作为列表的元素.
print([i for i in range(10)])  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

list1 = ['kid', 'qz', 'qq']
# 先执行for遍历将容器中的值赋给i, i在做字符串拼接.
print([i + '_vip' for i in list1])  # ['kid_vip', 'qz_vip', 'qq_vip']

list1 = ['kid_vip', 'qz_vip', 'qq_vip']
# 先执行for遍历将容器中的值赋给i, i做移除字符串操作.
print([i.strip('_vip') for i in list1])  # ['kid', 'qz', 'qq']

# 复杂格式, 先执行for, 在执行if语句, 
list1 = ['kid_vip', 'qz_vip', 'qq_vip']
# 遍历列表, 取出元素马上进行if判断
print([i.strip('_vip') for i in list1 if i == 'kid_vip'])  # ['kid']

# 面试题
list1 = [lambda x: x + i for i in range(10)]
# 保存十个匿名函数
print(list1)
# [<function . at 0x000001D0B252F700>, ...]
# 调用第一个匿名函数
print(list1[0](10))  # 19
# 调用第二个匿名函数
print(list1[1](10))  # 19
# 调用第三个匿名函数
print(list1[2](10))  # 19

函数具有调用时才查找变量的特性, 在没调用之前它不会保存变量的具体值.
只有调用它的时候, 才逐一去找这些变量的具体值, 无所调用第几个匿名函数, 变量i已经循环完毕, 变成9.
# i=i 将i的值保存下来
list1 = [lambda x, i=i: x + i for i in range(10)]
print(list1[0](10))  # 10
print(list1[1](10))  # 11

2.2 字典生成式
# 字典创建方式
list1 = ['name', 'age', 'hobby']
list2 = ['kid', 18, 'red']

dic = {}
for i in range(len(list1)):
    dic[list1[i]] = list2[i]

print(dic)  # {'name': 'kid', 'age': 18, 'hobby': 'red'}
# 字典生成式
list1 = ['name', 'age', 'hobby']
list2 = ['kid', 18, 'red']


res = {list1[i]: list2[i] for i in range(len(list1))}  

print(res)  # {'name': 'kid', 'age': 18, 'hobby': 'red'}
2.3 集合生成式
list1 = ['name', 'age', 'hobby']

res = { i for i in list1}  
print(res) # {'hobby', 'age', 'name'}

2.4 元组生成式
()python中的生成器占用, 想要使用元组生成式在括号前面加上关键字tuple.
print(tuple(x for x in range(5)))  # (0, 1, 2, 3, 4)

3. 可迭代对象

迭代: 迭代即更新的意思, 每次更新都必须依赖上一次的结果, 其目是为了更逼近所需结果.
可迭代对象: 即可以进行迭代操作的一类对象, 能被for循环遍历取值的对象的都是可迭代对象.
内置isinstance(Object, Iterable)函数: 判断一个对象是否是可迭代对象.

管方解释说只能识别对象含有.__iter__()方法的对象,
那么意味着对象含有.__iter__()方法都是'可迭代对象'.

一般情况下所有的双下方法都会有一个与之对应的简化写法的函数.
: .__iter__()方法 --> iter()函数.
针对双下划线开头, 双下滑线结尾的方法标准的读法是'双下方法名', : 双下iter.
# 导入Iterable类
from collections import Iterable

str1 = ''
print(isinstance(str1, Iterable))  # True

str1.__iter__()


4. 迭代器

迭代器: 是一种通过迭代方式取值的方式, 从序列的第⼀个元素开始访问, 直到所有的元素被访问完, 结束.
迭代器可以记住遍历对象所在的位置, 只能往后取值, 不会后退. 
通常含有.__next__()方法的对象就是迭代器.
4.1 迭代器的优缺点
迭代器的优点: 节省内存, 迭代器在内存中只占一个数据的空间.
每次通过next()取值, 便会计算出一个值, 内存空间内加载当前的值, 将上一条值舍弃.

迭代器的缺点: 不能直观的查看里面的数据, 取值时不走回头路, 只能一直向下取值.
取值方式对比:
迭代取值: 是通用取值方式, 不依赖于索引取值. 特点: 永远都是往下一个取值, 无法重复获取.
索引取值: 不通用取值方式, 有序的容器类型, 才能使用. 特点: 可以重复取值.
4.2 迭代器的惰性机制
迭代器的惰性机制: next一次, 取一个值, 绝不过多取值.
迭代是数据处理的基石, 内存中放不下的数据集时,
需要一种惰性获取数据项的方式, 即按需一次获取一个数据项, 这就是迭代器模式.
4.3 生成迭代器
字符串, 列表, 字典, 元组, 集合, 文件都是可迭代对象, 通过.__iter__()方法实例化得到迭代器.
迭代取值固定语法: for element in Iterable, for语句内部通过.__next__()方法获取迭代器对象的元素.
直接使用.__next__()方法取值, 一次取出一个, 取完再取则报错:
: StopIteration.
: 停止迭代.
4.4 文本IO包装器
文件对象本身就是迭代器对象.
"""
a.txt内容:
1
2
3
"""

# 获取文件句柄
rf = open('a.txt', 'rt')
text_io_wrapper = rf.__iter__()

print(text_io_wrapper, type(text_io_wrapper))
#  
print(text_io_wrapper.__next__())  # 1 和换行符号 


# 文件对象本身就是迭代器对象.
print(rf, type(rf))
#  
print(rf.__next__())  # 2 和换行符号 


4.5 字符串迭代器
# 字符串迭代器
str_iterator = '123'.__iter__()
print(str_iterator, type(str_iterator))
#  

# 迭代取值
print(str_iterator.__next__())  # 1
print(str_iterator.__next__())  # 2
print(str_iterator.__next__())  # 3
print(str_iterator.__next__())  # 报错: StopIteration

4.6 列表迭代器
# 列表迭代器
list_iterator = [1, 2, 3].__iter__()
print(list_iterator, type(list_iterator))
#  

print(list_iterator.__next__())  # 1
print(list_iterator.__next__())  # 2
print(list_iterator.__next__())  # 3

4.7 字典键迭代器
# 字典键迭代器
dict_keyiterator = {'k1': 'v1', 'k2': 'v2'}.__iter__()
print(dict_keyiterator, type(dict_keyiterator))
#  


print(dict_keyiterator.__next__())  # k1
print(dict_keyiterator.__next__())  # k2

4.8 元组迭代器
# 元组迭代器
tuple_iterator = (1, 2, 3).__iter__()
print(tuple_iterator, type(tuple_iterator))
#  

print(tuple_iterator.__next__())  # 1
print(tuple_iterator.__next__())  # 2
print(tuple_iterator.__next__())  # 3

4.9 集合迭代器
# 集合迭代器
set_iterator = {1, 2, 3}.__iter__()
print(set_iterator, type(set_iterator))
#  

print(set_iterator.__next__())  # 1
print(set_iterator.__next__())  # 2
print(set_iterator.__next__())  # 3

4.10 注意事项
每次执行.__iter__()方法都生成一个新的迭代器对象, 都是第一次迭代取到的值是同一个值.
str1 = 'abc'

print(str1.__iter__().__next__())  # a
print(str1.__iter__().__next__())  # a
print(str1.__iter__().__next__())  # a
print(str1.__iter__().__next__())  # a

4.11 模拟for循环
1. 执行步骤
for循环执行步骤:
* 1. in关键字后面的序列类型调用.__iter__()方法转为迭代器对象.
* 2. 循环执行.__next__()方法取值.
* 3. 取完之后再次执行.__next__()就会报错, 会自动捕获错误并处理.
2. 异常捕获
异常: 是一个事件, 该事件会在程序执行过程中发生, 影响程序的正常执行.
在解释器无法正常处理程序时就会发生异常, 程序会终止执行.

错误类型: 
* 1. 语法错误: 解释器在解释源文件语句前会进行语法检测出的错误, 源代码不符合Python的编写规范.
* 2. 逻辑错误: 程序执行之后出现的错误, 语法正确, 但其代码违反逻辑规则而产生的错误, 种类很多...
异常信息三个组成部分:
* 1. 回溯信息: 最近一次错误所在行:
Traceback (most recent call last):
  File "xxx", line 1, in <module>
    ...
* 2. 错误类型: 
NameError: xxx...
* 3. 冒号后面的错误的详情原因, 解决bug的关键.

语法错误必须立刻修改, 否则程序无法执行. 出现逻辑错误可以进行针对性处理, 觉得是都让程序继续运行.
异常处理语法格式:
try:
	被检测的代码.
except 错误类型 as 接收错误信息的变量:
	被检测代码出错后的处理机制.
try:
    name
except NameError as e:
    print(e, '结束程序运行!')  # name 'name' is not defined
    
Exception 是所有异常的父类, 所有异常都可以检测到.
3. while循环遍历迭代器对象
# while方法模拟for循环
l1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55]
# 生成迭代器对象
list_iterator = l1.__iter__()

count = len(l1)
while count:
    i = list_iterator.__next__()
    print(i)
    count -= 1
    
# 异常捕获停止跌倒
l1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55]
list1 = l1.__iter__()
while True:
    try:
        print(list1.__next__())
    except Exception:
        break
        
* try语句检测程序会占用额外的资源, 执行一次就有一句try语句, 不推荐使用大量的try语句.
4. 递归遍历迭代器对象
l1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55]
# 生成迭代器对象
list_iterator = l1.__iter__()

count = len(l1)


def func(counts):
    if counts == 0:
        return
    i = list_iterator.__next__()
    print(i)
    return func(counts - 1)


func(count)

l1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55]
# 生成迭代器对象
list1 = l1.__iter__()


def func(list1):
    try:
        print(list1.__next__())
    except Exception:
        return
    return func(list1)


func(list1)

5. 生成器

有时候序列或集合的元素的非常多, 如果所有对象都直接存放在内存中, 会占用大量内存的资源.
于是设计出一种按某种顺序推导元素的算法, 需要哪个元素就迭代计算出哪个元素, 而不必创建完整的元素集合.
每次仅占用一个元素的空间, 元素被取出后, 给下一个计算结果使用, 从而节省大量的空间.
在Python中这种计算出元素的机制, 称为生成器: generator. 

生成器属于自定义迭代器, 生成器保存的是计算元素的算法或表达式, 可以使用for迭代取值.
5.1 生成器生成式
Python规定生成器生成式使用()圆括号.
生成器生成式能够快速生成一个生成器, 保存了计算元素的算法.
只有在迭代取值的时候才会执行计算元素的算法, 每次仅提供一个元素.
g1 = (i for i in range(10))
print(g1)  # generator 生成器

print(next(g1))  # 0
print(next(g1))  # 1
print(next(g1))  # 2


# 迭代取值计算出元素
for i in g1:
    print(i, end=' ')  # 3 4 5 6 7 8 9

5.2 生成器的特点
生成器的特点, 在使用的时候才会计算, 通过一个案例来讲解:
# a.txt
123456789
123456789
123456789
with open('a.txt') as f:
    # 生成器生成式
    g1 =(len(line) for line in f)  # 这一步其实没有读取文件, 简单来说就是将表达式保存什么都没干.

print(sum(g1))  # 这一步才执行生成器, 去读取文件, 但是文件句柄已经回收了

为何报错?ValueError: I/O operation on closed file.
ValueError: 对已关闭文件的 IO 操作.

迭代器只有在使用的时候才执行, 元组生成式走了过场什么都什么做.

你以为代码是: (换行'
'算一个字符.)
g1 = (10, 10, 9) 
其实代码是这样的:
g1 = (len(for line in f), len(for line in f), len(for line in f)
当需要执行的时候, 文件的io操作已经关闭了, 所有报错.
with open('a.txt') as f:
    g1 = (len(line) for line in f)  
    
    # 在没有回收文件句柄的时候代码就可以正常的执行.
    print(sum(g1))  # 29
    
5.3 yield关键字
yield关键字: 在函数内使用, 调用函数时, 函数不会执行, 而是返回一个生成器.

yield的作用:
* 1. 当函数体内含有yield关键字时, 那么在调用函数的时候, 函数体代码不会执行, 而是返回一个生成器.
* 2. 可以使用yield设置返回值, 也就是生成器的元素, 多个值会组织成元组, 不写默认返回None.
* 3. 第三种传值的方式, yeied可以传递参数.

1. 返回生成器
当函数体内含有yield关键字时, 那么在调用函数的时候, 函数体代码不会执行, 而是直接返回一个生成器.
# 函数中有yield语句
def func():
    print('你好')
    yield

    
# 函数内体代码执行了, 它的状态没有被展示出来.
res = func()  
# 返回生成器对象
print(res, type(res))  #  

2. 迭代取值
调用.__next__()方法取值时才会执行函数中的语句, 遇到yield语句返回一个值, 并保存当前函数的执行状态.
下一个次.__next__()方法则从上次暂停的位置处往回执行, 后续没有yield则执行到底.
def func():
    num = 0
    print(num)
    # 通过yield返回值
    yield 'yield1'

    num += 1
    print(num)
    # 通过yield返回值
    yield 'yield2'

    num += 1
    print(num)


# 接收生成器
res = func()
# 迭代取值, 遇到yield语句则返回跟随它的值, 并且会暂停函数的运行保存函数的状态.
print(res.__next__())
# 再次迭代取值, 让函数继续运行, 从上次暂停的位置开始往后执行.
print(res.__next__())
# 函数还没执行完, 还需要通过.__next__()方法让程序继续完后执行,
# 但执行之后.__next__()方法要求必须有返回值(也是就说说必须要有yield语句),
# 后续并没有yield语句则会报错: StopIteration.
try:
    res.__next__()
# 报错没有提示信息, 空白.
except StopIteration as e:
    print(e)

运行工具窗口显示:
0
yield1
1
yield2
2

5.5 Python 迭代器与生成器插图

# yield返回值
def func():
    num = 0
    print(num)
    # 通过yield返回值
    yield 'yield1'

    num += 1
    print(num)
    # 通过yield返回值
    yield 'yield2'

    num += 1
    print(num)


# 接收生成器
res = func()
# for迭代取值
for i in res:
    print(i)

# yield默认返回值
def func():

    print(1)
    # 默认返回None
    yield


# 接收生成器
res = func()
# 迭代取值
for i in res:
    print(i)  # None
    
3. yield传参
yield传参格式:
生成器函数.send(参数1, ···), .send()方法可以让函数运行(包括yield停止的情况), 遇到yield停止.
函数中接收参数:
value=yield  
程序中第一次使用生成器调用.send()方法时, 不能使用带参数的send()函数.
def my_ge():

    num = yield
    print(num)  # None


res = my_ge()
# 使用.send方法让函数运行
print(res.send(None))

# 第一次调用传递给None的参数
def my_ge():

    num = yield
    print(num)


res = my_ge()
print(res.send(1))  # 报错

运行工具窗口显示:
Traceback (most recent call last):
  File "C:\Users600\PycharmProjects	est	est.py", line 8, in <module>
    print(res.send(1))
TypeError: can't send non-None value to a just-started generator

: TypeError: can't send non-None value to a just-started generator.
: 无法向刚启动的生成器发送非None值,
yield语句的完整执行顺序: 
* 0. 调用含有yield的函数, 函数体代码不会执行, 而是返回一个生成器, 这里可以称为生成器函数.
* 1. 使用.__next__()方法时会执行函数体代码
* 2. 遇到yield语句则返回跟随它的值(双下next方法就是要这个值), 并且会暂停函数的运行保存函数的状态.
* 3. 生成器函数通过.send方法()返回一个参数给yield语句执行的结果.
* 4. 使用变量接收yield语句执行的结果.
* 5. 后续可以使用send传递的参数.

执行过yield语句后, 才能使用.send()方法传值给yield, 
这个值作可以理解为yield语句执行的结果, 可以使用变量接收.
def my_ge():
    while True:
        print('执行my_ge函数!')
        # num接收 yield语句执行的结果
        num = yield '可以传递了!'
        # 使用num
        print(num)


res = my_ge()

print(res.__next__())  # 遇到yield停止, 打印yield返回的结果
res.send(1)  # 传递参数给 yield
res.send(2)  # 传递参数给 yield

5.5 Python 迭代器与生成器插图(1)

5.4 实例
def func1():
    for i in range(10):
        yield i + 1 - 1 * 2 / 2


def func2():
    res = func1()

    for i in res:
        print(i)


func2()

# 模仿range函数
def my_range(start_num, end_num=None, len_num=1):
    # 没有提供终止位参数, 则将起始为参数作为终止位参数, 将起始位设置为0.
    if not end_num:
        end_num = start_num
        start_num = 0
	
    # while循环, 设置循环停止添加, 当起始位的值等于终止位的值时停止循环.
    while start_num < end_num:
        # 返回参数
        yield start_num
        # 参数自增, 默认为1
        start_num += len_num


for i in my_range(1, 10, 2):
    print(i)
    
# 面试题
def test():
    for i in range(4):
        yield i


# 得到一个生成器
g = test()


for n in [1, 10]:
    # 再次得到一个生成器, 生成器中的for不会执行, 它不会保存值只保留表达式.
    g = (i + n for i in g)
"""
n = 1 
g = (i + n for i in g)
n = 10
g = (i + n for i in (i + n for i in test()))
"""

res = list(g)
"""
g = (i + n for i in (i + n for i in (0, 1, 2, 3))  # n 为 10.
g = (i + 10 for i in (i + 10 for in (0, 1, 2, 3))  
g = (i + 10 for i in (10, 11, 12, 13)
g = (10+10, 11+10, 12+10, 13+10
"""
print(res)  # 20 21 22 23

6. 练习

1. 使用递归打印斐波那契数列(前两个数的和得到第三个数, : 1, 2, 3, 5, 8, 13..)
def num(x, y):
    if y > 100:  # 结束条件
        return

    # z = y
    # y = x + y
    # x = z
    
    y, x = y + x, y
    print(y, end=' ')  # 1 2 3 5 8 13 21 34 55 89 144 
    
    num(x, y)
    
num(0, 1)
2. 一个嵌套很多层的列表, 用递归取出所有的值.
list1 = [1, 2, [3, [4, 5, 6, [7, 8, [9, 10, [11, 12, 13, [14, 15]]]]]]]


def get_num(list1):
    if not len(list1):  # 结束条件
        return

    for i in list1:
        # i是整数就打印
        if type(i) == int:
            print(i, end=' ')
        # 否则就 递归
        else:
            get_num(i)


get_num(list1)
本站无任何商业行为
个人在线分享-虚灵IT资料分享 » 5.5 Python 迭代器与生成器
E-->