(一) python可迭代对象,迭代器,生成器

一、什么是可迭代对象?(底层实现逻辑,知道什么是可迭代对象就好,下方代码仅供理解,理解不了没关系,只要知道下面的文字描述就ok)

答:能够进行迭代逐一返回其成员项的对象成为可迭代对象。可迭代对象的例子包括

  • 所有序列类型:如 list 、str和tuple
  • 非序列类型:dict、set、文件对象
  •  实现了__iter__()方法的任意对象(可迭代协议)
  • 实现了序列语义的__getitem__()方法的任意对象

注意点 :实现了__getitem__()方法的被称为可迭代对象(支持迭代操作),但不是Iterable类型。

我们自己手写一个可迭代对象:

# 可迭代对象,迭代器,生成器
from collections.abc import Iterable, Iterator, Generator


# 可迭代对象 python里面什么叫迭代?通过循环,能够通过for循环进行迭代操作的对象

class Mylist:
    def __iter__(self):
        return iter([11, 22, 33, 44])


if __name__ == '__main__':
    m = Mylist()
    print(isinstance(m, Iterable))

    # for循环不管是遍历元祖还是列表还是字典,首先做的是什么?首先调的是 m对象的iter方法,
    # 然后遍历的是什么东西呢?遍历的是m这个对象里面的iter方法所返回的一个迭代器
    for i in m:
        print(i)





# 实现了__getitem__()方法
class Mylist2:
    """自定义序列类"""
    li = [11, 22, 33, 44]

    def __getitem__(self, item):
        return self.li[item]


if __name__ == '__main__':
    m2 = Mylist2()
    for i in m2:
        print(i)

    print(isinstance(m2, Iterable))

二、什么是迭代器? 

答:从应用层面上来讲,能够通过for循环进行迭代操作,也能够通过next方法进行迭代操作;

        从原理层面上来说,实现了迭代器协议的对象(实现了__inter__方法),也实现了__next__方法。

  • 1、实现了迭代器协议的对象,就是一个迭代器
  • 2、所有的可迭代对象,都可以通过内置函数iter()转换为迭代器
  • 3、迭代器对象能够使用 内置函数next进行迭代操作
  • 4、所有的迭代器都是可迭代对象,因为迭代器协议包含了迭代协议

1、略

2、 由上图可以看出,res是可迭代对象,但是他不是一个迭代器,那怎么将他转换为迭代器呢?2上所述,可以通过内置函数iter方法转化,那我们试一下。

3、 迭代器对象能够使用 内置函数next进行迭代操作

 

 如果继续next遍历一次,那么就会出来一个y

如果使用dic4.keys进行next迭代,那么就会报错,因为他不是一个迭代器,他只是一个可迭代对象。

 

 注意:由上图看出来,迭代器Iterator是继承自Iterable,也有__iter__方法,所以说,可迭代对象不一定是可迭代器,但迭代器肯定是可迭代对象。

(1)我们通过dir内置函数方法查看这个可迭代对象有什么方法:

 (2)__length_hint__这个方法是用来获取可迭代对象这个容器里面有多少数据

# 获取迭代器内部的数据长度
li1 = iter([11, 22, 33, 44])
print(li1.__length_hint__())



# 打印结果
4

(3) 通过next方法或者__next__()进行迭代操作。

(4)迭代到这个容器里面没有数据了,就会报错 StopIteration

 (5)__setstate__()   这个方法是设置默认从哪里开始进行迭代

     ①

# 设置从索引为2的地方开始迭代
li2 = [11, 22, 33, 44]
li2.__setstate__(2)



# 打印结果
33

②这个时候,我们设置了从2开始进行迭代操作,然后我们通过for循环试试他打印的是多少

# 设置从索引为2的地方开始迭代
li2 = [11, 22, 33, 44]
li2.__setstate__(2)

for i in li2:
    print('i的值为',i)

# 打印结果
33

i的值为33
i的值为44

很明显,我们通过__setstate__(2)更改了他迭代的起始位置。所以打印了33,44

③迭代之前获取到的数据的长度4,迭代之后获取到的数据长度3,当数据长度为0的时候,再去做迭代操作,就会抛出异常StopIteration

# 设置从索引为2的地方开始迭代
li1 = iter([111, 22, 33, 44])

print(li1.__length_hint__())

print(next(li1))

print(li1.__length_hint__())



# 打印结果
4
111
3

4、迭代器到底用来干什么的?(迭代器的实际运用)

读取文件里的内容,一行一行读取,放在迭代器里面,节省内存的开销。

 如果想进行批量迭代的话,可以通过for循环

with open(r'C:\project\py01.py', 'r', encoding='utf-8') as f:
    res = iter(f.readlines())


for i in res:
    print(i)

三、什么是生成器?

 

 生成器里面有哪些方法?__iter__和__next__方法是继承自 Iterator迭代器的,独有的三个方法send(往生成器里传入数据)、throw(往生成器里面抛异常)、close(关闭生成器)。

(1)生成器的定义

 ① 生成器的定义

 ② 生成器表达式

从上面打印结果可以看得出来,生成器支持next进行迭代操作(生成数据),也支持for循环遍历

# 生成器表达式  
g1 = (i for i in range(10))

print(next(g1))
print(next(g1))

for i in g1:
    print('i的值为', i)



# 打印结果
0
1
i的值为 2
i的值为 3
i的值为 4
i的值为 5
i的值为 6
i的值为 7
i的值为 8
i的值为 9


# 从上面打印结果可以看得出来,生成器支持next进行迭代操作(生成数据),也支持for循环遍历

  ③ 生成器函数: yield关键字,

生成器函数定义:在函数中通过使用关键字yield来实现生成器函数,不会执行里面的代码,返回的是一个生成器。

打断点调试的时候,发现不会走到func01方法里面,压根就不会调用这个函数,直接返回一个生成器。

 --------------------------------------------------------------------------------------------------------------------------------

(2)生成器的迭代操作(生成数据)

但是假如我们通过next对生成器进行迭代操作(生成数据),我们打断电调试发现会执行到这个方法里面,但是执行到yield之后,并没有往下执行print("----------end----------")这一行代码,就直接从方法里出来了。

---------------------------------------------------------------------------------------------------------------------------------

我们打印这个next(res)发现这个值为 None,为什么因为这个yield 后面没有值,然后遇到yield也不回继续往下执行,所以不会打印111,222,333,end

def func01():
    print('----------start----------')
    yield
    print('-----------111------------')
    yield 'python'
    print('-----------222------------')
    yield 'java'
    print('-----------333------------')
    yield
    print('-----------end------------')


res = func01()
print('第一次迭代', next(res))



# 打印结果
----------start----------
第一次迭代 None

 --------------------------------------------------------------------------------------------------------------------------------

然后我们在生成器函数里 在yield 后面添加值的时候,我们看执行的结果。

接着往下执行:输出111,然后输出第二个yield后面的python,然后从方法中出来。

由此可以看出:有几个yield 这个生成器就能执行几次迭代操作。

 --------------------------------------------------------------------------------------------------------------------------------

注意:这个yield关键字只能用在函数里面,如果用在其他地方就会报语法错误:SyntaxError: 'yield' outside function。

def func01():
    print('----------start----------')
    print('-----------end------------')

res = func01()
print(res)

# 打印结果
----------start----------
-----------end------------
None

# ------------------------------------------------------------------------
## 这个时候我们在两个print中间加个yield
def func01():
    print('----------start----------')
    yield
    print('-----------end------------')

res = func01()
print(res)

# 打印结果
<generator object func01 at 0x0000020B67555EE0>

生成器进行迭代操作实际运用(特殊用法):

def func01():
    for i in range(100):
        yield i


g1 = func01()
# 1、使用next进行迭代操作
print(next(g1))
print(next(g1))
print(next(g1))
print(next(g1))


# 打印结果
0
1
2
3

(3)生成器内部数据交互

① 使用send方法进行迭代(send方法可以往生成器内部传递数据)

常用场景是根据我们给的数据,然后给我们生成新的数据出来。

def func01():
    for i in range(100):
        s = yield i
        print('s值为:', s)


g1 = func01()
# 1、使用next进行迭代操作
# print(next(g1))
# print(next(g1))
# print(next(g1))
# print(next(g1))

# 2、使用send方法进行迭代
next(g1) # 启用生成器
print('-----------------------------------------------------')

res = g1.send('musen')
print('res:', res)
print('-----------------------------------------------------')
res = g1.send('java')
print('res:', res)



# 打印结果
-----------------------------------------------------
s值为: musen
res: 1
-----------------------------------------------------
s值为: java
res: 2

由此可以看出来,send里面传入的参数实际是给 yield前面的s的,yield后面的值 实际是遍历的值赋给res。

② 关闭生成器close

def func2():
    res = {"data": None, "title": None}
    for i in range(100):
        s = yield res
        res["data"] = s
        res['title'] = '用例' + str(i)


g2 = func2()

next(g2)
print(g2.send('musen'))
print(g2.send('xiaojian'))
print(g2.send('yuze'))
# 关闭生成器
g2.close()
print(g2.send('wangqiang'))



# 打印结果
{'data': 'musen', 'title': '用例0'}
{'data': 'xiaojian', 'title': '用例1'}
{'data': 'yuze', 'title': '用例2'}

Traceback (most recent call last):
  File "/Users/wangqiang/PycharmProjects/py09Class/复习/py09_03day/demo3_生成器特殊的方法.py", line 24, in <module>
    print(g2.send('wangqiang'))
StopIteration

Process finished with exit code 1

③ throw:主动往生成器内部抛异常(了解即可,没有实际运用场景,咱们还没有达到那种境界 哈哈哈) 

 g2.throw(ValueError) ,相当于在生成器内部执行 raise 异常类型

def func2():
    res = {"data": None, "title": None}
    for i in range(100):
        s = yield res
        res["data"] = s
        res['title'] = '用例' + str(i)


g2 = func2()

next(g2)
print(g2.send('musen'))
print(g2.send('xiaojian'))
print(g2.send('yuze'))
# 关闭生成器
g2.close()
print(g2.send('wangqiang'))

g2.throw(ValueError)


# 打印结果
{'data': 'musen', 'title': '用例0'}
{'data': 'xiaojian', 'title': '用例1'}
{'data': 'yuze', 'title': '用例2'}

Traceback (most recent call last):
  File "/Users/wangqiang/PycharmProjects/py09Class/复习/py09_03day/demo3_生成器特殊的方法.py", line 23, in <module>
    g2.throw(ValueError)
  File "/Users/wangqiang/PycharmProjects/py09Class/复习/py09_03day/demo3_生成器特殊的方法.py", line 12, in func2
    s = yield res
ValueError

(4)实战场景

1、实现一个生成{data:xxxx}的生成器

g2.send('zhanzhao'):执行顺序,先把'zhanzhao'赋值给s,然后再执行res['data']=s,再跑到yield的时候把res返回。

# 实现一个生成{data:xxxx}的生成器
def func2():
    res = {'data': None}
    for i in range(100):
        s = yield res
        res['data'] = s


g2 = func2()
# 启动生成器
next(g2)

# 向生成器中传入数据
res1 = g2.send('zhanzhao')
print(res1)



# 打印结果:
{'data': 'zhanzhao'}

(5)课后练习(附答案)

# 1、现在有一个列表   li = [11,21,4,55,6,67,123,54,66,9,90,56,34,22],
# 请将 大于5的数据过滤出来,然后除以2取余数,结果放到一个生成器中

# li = [11, 21, 4, 55, 6, 67, 123, 54, 66, 9, 90, 56, 34, 22]
# g1 = [i % 2 for i in li if i > 5]
# print(g1)


# 2、定义一个可以使用send传入域名,自动生成一个在前面加上http://,在后面加上路径/user/login的url地址,
# 生成器最多可以生成5个url,生成5条数据之后再去生成,则报错StopIteration
# 使用案例:
# 例如:
# res = g.send('www.baidu.com')
# # 生成数据res为:http://www.baidu.com/user/login'

# 方法1:
def func1():
    res = None
    for i in range(6):
        s = yield res
        res = "http://" + s + "/user/login"


g1 = func1()
# 启动生成器
next(g1)

print(g1.send("www.baidu.com"))
print(g1.send("www.guge.com"))
print(g1.send("www.kuaishou.com"))

# 方法2:
def func2():
    address = None
    url = f'(http://{address}/user/login)'
    for i in range(6):
        s = yield url
        url = f'(http://{s}/user/login)'


g2 = func2()
# 1、使用next启动生成器
next(g2)
fin = g2.send('www.baidu.com')
print(fin)
fin2 = g2.send('www.baidu2.com')
print(fin2)
fin3 = g2.send('www.baidu3.com')
print(fin3)
fin4 = g2.send('www.baidu4.com')
print(fin4)
fin5 = g2.send('www.baidu5.com')
print(fin5)
fin6 = g2.send('www.baidu6.com')
print(fin6)


# 3、面试笔试扩展题
# 有一个正整数列表(数据是无序的,并且允许有相等的整数存在),
# 编写一个能实现下列功能的函数,传入列表array,和正整数X,返回下面要求的2个数据
# def func(array, x)
#     '''逻辑代码'''
#     return count, li
# 1、统计并返回在列表中,比正整数x大的数有几个(相同的数只计算一次),并返回-----返回值中的的count
# 2、计算列表中比正整数X小的所有偶数,并返回  -----------返回值中的li
def func(array, x):
    null_list = []
    for i in array:
        if i > x:
            if i not in null_list:
                null_list.append(i)
    li = [j for j in array if j < x and j % 2 == 0]
    return null_list, len(null_list), li


print(func([10, 22, 33, 44], 22))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值