生成器和迭代器

可迭代对象

什么是迭代?
	迭代言外之意就是更新换代,每次的更新都是要依赖于上一次的结果
    
# 什么又是可迭代对象呢?
	就是内置有__iter__()方法的都可以称之为是可迭代对象
    	"内置":可以直接点出来的就称之为是内置,既然可以点出来就可以直接拿来使用
        __name__:属性
        __iter__():方法
    """
    	__iter__()
    		双下划线开头的方法我们是第一次遇到,他是一种特殊的方法
    	像双下划线开头的方法在面相对象里面最常见
    	用法跟普通方法都是一样的
    """
 # 我们所学的八种基本数据类型都有哪些是可迭代对象呢?
"""
	是可迭代对象的有:
		字符串、列表、元组、字典、集合等
		整型、浮点型、布尔值都不是
	ff = open('a.txt', 'w', encoding='utf8')
		文件对象也是可迭代对象,因为内置的有__iter__()方法
"""

可迭代对象调用__iter__()方法就变成了"迭代器对象"
print(d.__iter__())
print(iter(d))


def iter(obj):
    return obj.__iter__()
# print(len(d))
# print(d.__len__())

def len(obj):
    return obj.__len__()
"""
	一般情况下,双下滑先开头的方法都有一个与之对于的简化方法 名字()
"""

迭代器对象

迭代器对象:迭代器

什么是迭代器对象?
	既内置了__iter__()方法,又内置了__next__方法就是迭代器对象
 
"""文件对象本身就具备__iter__()和__next__()方法"""
即是可迭代对象又是迭代器对象

# 到底都有哪些数据类型是迭代器对象
str、list、dict、tuple、set、文件等

# 迭代器对象有什么用呢
"""
    迭代器其实是一种不依赖于索引取值的方式!
"""

可迭代对象多次调用__iter__()方法之后还是迭代器对象,所以你只需要调用一次即可
l = [1, 2, 3, 4]
# print(l[0])
# print(l[1])
# print(l[2])

res1 = l.__iter__()
# print(res1.__next__())
# print(res1.__next__())
# print(res1.__next__())

print(next(res1))
print(next(res1))
print(next(res1))

d = {'username': 'ly', 'age': 18}
# res = d.__iter__()
# print(res.__next__())
# print(res.__next__())

# print(d.__iter__())
# print(d.__iter__().__iter__().__iter__().__iter__())

"""易错题"""
ll = [1, 2, 3, 4]
# res = ll.__iter__()
# print(res.__next__())
# print(res.__next__())
# print(res.__next__())
# print(res.__next__())
# print(res.__next__())  # StopIteration 当数据被取值完的时候,如果在次next会直接报错
print(ll.__iter__().__next__()) # 1
print(ll.__iter__().__next__()) # 1
print(ll.__iter__().__next__()) # 1 
print(ll.__iter__().__next__()) # 1

res = ll.__iter__()
print(res.__next__())
print(res.__next__())
print(res.__next__())
print(res.__next__())

异常捕获

try
	被监测的代码:一般是可能会发生的错误
except 错误类型1 as e
    	print(e)
except 错误类型2 as e
   	 print(e)
else:
    print('看一下else什么时候走的?')
finally:
    print('看一下finally什么时候走?')
"""
    没有异常的时候else会走,有异常的时候else不会走
    finally是不管有没有异常都会走
"""

# 这里面你只需要记住try和else不能单独使用,

for循环的原理

l = [1,2,3,4,5,6,7,8]
# 循环打印出列表中的每一个元素,但是不能使用for循环,必须要使用__next__()取值

l = [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
# 循环打印出列表中的每一个元素,但是不能使用for循环,必须要使用__next__()取值

res = l.__iter__()
while True:
    try:
        print(res.__next__())
    except Exception:
        break
相当于:        
for i in l:
    print(i)
    
for循环的原理:
	1. 先把关键字in后面的可迭代对象先调用__iter__()
    2. while循环,next()取值
    3. for的内部当出现异常的时候,做了捕捉处理:StopIteration的错误,break

迭代取值和索引取值的对比

迭代取值
	1. 不依赖于索引取值
	2. 只能从做往右依次取值,不能重复取值

索引取值
	1. 必须依赖于索引取值
    2. 可以重复取值
    	l[0]
    3. 必须是容器类型

生成器(迭代器的一种)

"""
生成器的背景:
	l = [1, 2, 3]
	我们现在定义的列表,内部元素都比较少,占用的内存也是比较少的,我们几乎可以忽略,但是,如果我现在让你定义一个列表,里面存放100w个元素,可是呢,我们只用到了前面或者后面几个元素,其他元素很少用,如果我们把这100w个元素都写在列表里,并且每次使用的使用都打印了,会出现什么问题?
	所以,这个时候就会占用大量的内存空间,这些空间就被浪费了,针对这个问题,该如何优化一下呢
优化的方法就是利用生成器:
	如果你不用这些数据,那么在你打印的时候,我给给你一个对象,不给你具体的数据,你什么时候使用这些数据,我在给你
"""

# 生成器解决的问题是什么/
	就是节省内存空间
range(10) 在python3中是一个生成器,节省了内存空间

# 到底如何使用

def index():
    print('from index')
    yield 123,234,345
"""函数里面只要出现了yield关键字,那么,该函数就有普通的函数变成了生成器,就不在执行该函数了"""
res=index()
# print(res)  # <generator object index at 0x000001F5008287B0>

# 要想使用生成器只需要使用next取值即可
# 我们使用next取值,每next一次,代码走到第一个yield处停止,
print(res.__next__())  # None (123, 234, 345)
# print(res.__next__())  # None  再次执行next,会从上一次yield的位置处往下继续走,走到遇到第二个yield停止

"""如果你打印next的结果,就会返回yield关键字后面的数据"""
# 当yield关键字后面的数据用逗号隔开,有多个的时候,会以元组的形式返回

自定义rangge功能

range(10)

range(1, 10)

range(1, 10, 2)
# range函数不能用了,我让你写一个跟range函数一样的功能? 使用生成器来做:yield

# range()

# None:假 not None
def my_range(start, stop=None, step=1):
    # 判断传了一个参数还是两个参数
    if not stop:
        stop = start # stop=10
        start = 0 # start =0

    while start < stop:
        yield start
        start+=step


res=my_range(1, 10)
# print(res.__next__())
# print(res.__next__())

# for i in my_range(10, 100, 2): # __next__
#     print(i)

for i in my_range(10): # __next__
    print(i)

yield关键的传参问题

def eat(name):
    print('%s正在干饭' % name)
    while True:
        food = yield
        print('%s正在吃%s' % (name, food))

# 函数里面只要有yield关键字,就不会执行函数,变成了生成器
res=eat('kevin')
# res.__next__()
res.__next__()

"""
1. 把参数传给了yield
2. 执行了__next__取值
"""
res.send('馒头')
res.send('馒头1')
res.send('馒头2')

return和yield的对比

return
	1. 函数遇到return直接终止运行
    2. return也可以返回多个值

yield
	1. 函数遇到yield代码不会立即终止运行,而是"停住"
    2. yield也可以返回多个值,以元组的形式返回
    3. yield可以把函数变成生成器,而且还支持传参

生成器表达式

列表生成式:
res = [i for i in range(10)]

生成器表达式:
res1 = (i for i in range(10))
print(res1) # <generator object <genexpr> at 0x0000018083EA3A50>

print(res1.__next__())
print(res1.__next__())
print(res1.__next__())
print(res1.__next__())
print(res1.__next__())

"""生成器表达式如果你不去__next__,是不会给你造出来数据的"""
迭代器和生成器什么关系:
	'''生成器他是特殊的一种迭代器'''
    
"""
	迭代器、生成器我们都可以把它们看成是"工厂"
	你什么时候要数据我们就设么时候给你生产
	上述这样做的原因:
		节省内存空间
"""

如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成

with open('db.txt','rb') as f:
    nums=(len(line) for line in f)
    total_size=sum(nums) # 依次执行next(nums),然后累加到一起得到结果=

面试题

# 求和
def add(n, i):
    return n + i
# 调用之前是函数 调用之后是生成器
def test():
    for i in range(4):
        yield i
g = test()  # 初始化生成器对象
for n in [1, 10]:
    g = (add(n, i) for i in g)
res = list(g)
print(res)

#A. res=[10,11,12,13]
#B. res=[11,12,13,14]
#C. res=[20,21,22,23]
#D. res=[21,22,23,24]

选C
posted @ 2023-10-05 21:46  苙萨汗  阅读(21)  评论(0)    收藏  举报