Python3 迭代器和生成器 生成器表达式和各种推导式

迭代器

迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有元素被访问晚结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:iter()和next()

可迭代对象:

我们将遵循可迭代协议的称为可迭代对象。例如:str、list、tuple、dict、set

下面我们看一段错误代码来解释可迭代对象:

对的:

s='abc'
for i in s:
    print(i)



输出:
a
b
c

错的:

for i in 123:
    print(i)




结果:
Traceback (most recent call last):
  File "E:/pycharm file/Python学习之路/迭代器/diedaiqi.py", line 9, in <module>
    for i in 123:
TypeError: 'int' object is not iterable

注意:报错信息提示“ ‘int’ object is not iterable” 意思是整数类型对象是不可迭代的。iterable表示可迭代的。

判断数据类型是否符合可迭代协议(dir函数)

那么,如何判断你的数据类型是否符合可迭代协议。我们可以通过dir函数来查看类中定义好的所有方法。

s="我的选择"
print(dir(s)) #可以打印对象中的方法和函数
print(dir(str))#也可以打印类中申明的方法和函数

在打印结果中查看__iter__若果存在,那么这个类的对象就是一个可迭代对象。

打印结果:

['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

Process finished with exit code 0

以同样的方式查看list、tuple、dict、set。我们都能找到__iter__函数,我们发现这几个可以进行for循环的都有__iter__函数,包括range也有。

判断迭代器(Iterator)和迭代对象(Iterable):isinstance()函数

我们还可以通过isinstance()函数来查看一个对象是什么类型的。(Iterable 可迭代对象;Iterator  迭代器)

l=[1,2,3]
l_iter=l.__iter__()
from collections import Iterable
from collections import Iterator
print(isinstance(l,Iterable))
print(isinstance(l,Iterator))
print(isinstance(l_iter,Iterable))
print(isinstance(l_iter,Iterator))


True
False
True
True

如果对象中有__iter__函数,那么这个对象遵守可迭代协议。就可以获取相应的迭代器。__iter__是获取对象的迭代器。我们使用迭代器中的__next__()来获取一个迭代器中的元素。

s="我爱中国"
c=s.__iter__()  #获取迭代器
print(c.__next__()) #使用迭代器进行迭代,获取第一个元素  #我
print(c.__next__()) #爱
print(c.__next__()) #中
print(c.__next__()) #国
print(c.__next__()) #StopIteration

由上面实例可知,当超过要迭代的元素长度时,会报错“ StopIteration ”

使用while循环+迭代器来模拟for循环(掌握)

lst=[1,2,3]
lst_iter=lst.__iter__()  #1.获取迭代器
while True:              #2.使用while循环获取数据
    try:
        data=lst_iter.__next__()  #3.使用迭代器的__next__()方法来获取数据
        print(data)
    except StopIteration:  #4.处理异常
        break

总结:

Iterable:可迭代对象,内部包含__iter__()函数

Iterator:迭代器。内部包含__iter__同时包含__next__()

迭代器的特点:

  • 1.节省内存
  • 2.惰性机制(只有当使用迭代器的__next__函数才会获取下一个元素)
  • 3.不能反复,只能向下执行

生成器

生成器实质就是迭代器

在python中有三种方式来获取生成器:

1.通过生成器函数

2.通过各种推导式来实现生成器

3.通过数据的转换获取生成器

首先,我们看一个很简单的函数:

def func():
    print("abc")
    return 111
ret=func()
print(ret)

结果:
abc
111

将函数中的return换成yield就是生成器

def func():
    print("abc")
    yield 111
ret=func()  #获取生成器
print(ret)

结果:
<generator object func at 0x000001F1AD448EB8>

当我们调用func()不再是执行函数,而是获取生成器,我们可以直接执行__next__()来执行以下生成器:

def func():
    print("娃哈哈")
    yield 1 #return 和yield 都可以返回数据
    print("呵呵呵")

gen=func() #不会执行函数  拿到的是生成器

ret=gen.__next__()  #会执行到下一个yield  第一次执行第一个yield  1
print(ret)
gen.__next__()       #执行到下一个yield  没有下一个  报错StopIteration

结果:

Traceback (most recent call last):
娃哈哈
  File "E:/pycharm file/Python学习之路/生成器/shengchengqi.py", line 24, in <module>
1
呵呵呵
    gen.__next__()       
StopIteration

当程序执行完最后一个yield,在后面再继续进行__next__()程序就会报错。

举一个例子:我在商店购买10000套衣服

一般写法比较直接,一次性拿到10000套衣服,但是这样占用内存较大,由于数据太大,就没写结果

def order():
    lst=[]
    for i in range(10000):
        lst.append("衣服" + str(i))
        print(lst)
    return lst

ll=order()

还有一种就是改用生成器,不占用内存,将衣服一套一套的拿。生成器就是一个一个的指向下一个,不会回去

def order():
    for i in range(10000):
        yield "衣服"+str(i)

g=order()  #获取生成器
n1=g.__next__()   #拿第一套衣服
print(n1)
n2=g.__next__()   #拿第二套衣服
print(n2)


结果:
衣服0
衣服1

send方法

send和__next__()一样都可以让生成器执行到下一个yield。不同的是send可以给上一个yield位置传值

接下来看一个例子:

def eat():
    print("早餐吃什么")
    a=yield 1
    print("a=",a)   #a=包子
    b=yield 2
    print("b=",b)   #b=馒头
    c=yield 3
    print("c=",c)   #c=油条
    yield "OVER"    #最后必须以yield收尾  最后一个yield不能传值

gen=eat()  #获取生成器
ret1=gen.__next__()  #因为没有上一个yield,不能传值,只能用__next__
print(ret1)
ret2=gen.send("包子")
print(ret2)
ret3=gen.send("馒头")
print(ret3)
ret4=gen.send("油条")
print(ret4)

结果:

早餐吃什么
1
a= 包子
2
b= 馒头
3
c= 油条
OVER

执行过程:

注意:send是给上一个yield传值

send和__next__()区别:

  • send 和next()都是让生成器向下执行一次
  • send可以给上一个yield的位置传值,不能给最后一个yield发送值,在第一次执行生成器代码时不能使用send()

生成器可以用for循环来获取内部的元素:

#生成器可以用for循环来获取内部元素
def fun():
    yield 666
    yield 888
    yield 999

for i in fun():  #fun()是生成器  for循环的内部一定有__next__()
    print(i)

"""
结果:
666
888
999
"""
print(list(fun()))   #list内部有__next__()
#[666, 888, 999]

生成器表达式

生成器表达式和列表推导式的语法基本上是一样的,只需要把 [] 替换成()

gen=(i*i for i in range(10))
print(gen)


结果:
<generator object <genexpr> at 0x0000026452B90518>

打印的结果是一个生成器,接下来使用for循环来循环这个生成器:

gen=(i*i for i in range(10))

#用for循环来循环生成器
for a in gen:
    print(a)

打印结果:

0
1
4
9
16
25
36
49
64
81

生成器表达式也可以进行筛选:

例1:

gen=(i*i for i in range(10) if i%3==0) #筛选出10以内能被3整除的数的平方
print(gen)
#用for循环来循环生成器
for a in gen:
    print(a)

结果:

0
9
36
81

例2:

#寻找名字中带有两个aa字母的名字
names=['qwe','ert','asd','saa','faa']
gen=(name for name in names if name.count("a")>=2)
for name in gen:
    print(name)

结果:

saa
faa

生成器表达式和列表推导式的区别:

  • 1.占用内存不一样。列表推导式比较耗内存。一次性加载;生成器表达式几乎不占用内存,使用的时候才分配和使用内存
  • 2.得到的值不一样。列表推导式得到的是一个列表;生成器表达式获取的是一个生成器

生成器的惰性机制:生成器只有在访问的时候才取值

各种推导式

字典推导式

推导出来的是字典

#字典推导式
dic={'a':1,'b':2}
new_dic={dic[key]:key for key in dic}
print(new_dic)


结果:
{1: 'a', 2: 'b'}
#从lst1中获取的数据和lst2中相应的位置的数据组成一个新字典
lst1=['aa','bb','cc']
lst2=['11','22','33']
dic={lst1[i]:lst2[i] for i in range(len(lst1))}
print(dic)

结果:

{'aa': '11', 'bb': '22', 'cc': '33'}

面试题:由[11,22,33,44]生成字典{0:11,1:22,2:33,3:44}

lst=[11,22,33,44]
dic={i:lst[i] for i in range(len(lst))}
print(dic)

结果:
{0: 11, 1: 22, 2: 33, 3: 44}

集合推导式

集合推导式可以直接生成一个集合。集合的特点:无序、不重复,所以集合推导式自带去重功能

lst=[1,-1,2,-2,3]
#绝对值去重
s={abs(i) for i in lst}
print(s)


结果:
{1, 2, 3}

推导式有:列表推导式、字典推导式、集合推导式,没有元组推导式

一个面试题 :难度系数较大  惰性机制 不到最后不会拿值

#求和
def add(a,b):
    return a + b

#生成器函数
def test():
    for r_i in range(4): #0-3
        yield r_i

g=test()#获取生成器

#两个g都是生成器

for n in [2,10]:
    g=(add(n,i) for i in g)  

print(list(g))


结果:
[20, 21, 22, 23]

分析:

for n in [2,10]:
    g=(add(n,i) for i in g)  

拆分后:

for n =2:
    g=(add(n,i) for i in g)
for n =10:
    g=(add(n,i) for i in g)  #此时的g得到的是n=2时的g值

因为生成器的惰性机制,不到最后不拿值,n=2时,没有取值,改写为:

for n =10:
    g=(add(n,i) for i in (add(n,i) for i in g))   #(add(n,i) for i in g)->(add(n,i) for i in 0,1,2,3)->add(10,0123)->10,11,12,13

        ->g=(add(n,i) for i in 10,11,12,13)  #i=10,11,12,13

       ->g=(add(n,i))  =>20,21,22,23

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值