鱼C论坛

 找回密码
 立即注册
查看: 1533|回复: 8

[已解决]可迭代对象是怎么理解

[复制链接]
发表于 2021-11-18 15:04:38 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
可迭代对象应该怎么理解呢,大神们
最佳答案
2021-11-18 19:04:02
本帖最后由 傻眼貓咪 于 2021-11-18 19:05 编辑
可迭代对象
可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for 循环中使用的对象。例如字符串也是可迭代的。

Python 的 4 种序列:
列表(list)
集合(set)
元祖(tuple)
字典(dictionary)

Python 字符串(string)

*以上序列和字符串都可迭代(被视为可迭代对象)

例子 1:序列
a = ['a', 'b', 'c', 'd', 'e'] # 列表
b = ('a', 'b', 'c', 'd', 'e') # 元祖
c = {'a', 'b', 'c', 'd', 'e'} # 集合
d = {'one': 'a', 'two': 'b', 'three': 'c', 'four': 'd', 'five': 'e'} # 字典

for i in a:
    print(i, end = " ")
print()

for i in b:
    print(i, end = " ")
print()

for i in c:
    print(i, end = " ")
print()

for i in d.items():
    print(i, end = " ")
print()
输出结果:
a b c d e 
a b c d e 
c d a e b 
('one', 'a') ('two', 'b') ('three', 'c') ('four', 'd') ('five', 'e') 
以上你会发现,除了集合(set)每次不按照顺序输出之外,其他都是有序输出的。这是因为集合(set)是无序的;列表和元祖是有序的,而字典虽说是无序(属於 Hash 的一种)但是当访问时,如 items()、keys()、或values()都会变成有序的(这是因为 Python 3.6 版本之后,字典插入 indices 索引(OderedDict 元素)变得有序。而之前的 2.6 版本字典是乱序的,相关讲解可到 Python 官方自行查询)

例子 2:字符串
arr = "bananaAPPLEflower"

for i in arr:
    print(i, end = " ")
输出结果:
b a n a n a A P P L E f l o w e r 



除了上述以外,也可以自定义迭代器(定义一个迭代对象),如:
def myFunction():
    i = 10
    while i > 0:
        i -= 1
        yield i # yield 用于迭代

a = myFunction()

for i in a:
    print(i, end = " ")
输出结果:
9 8 7 6 5 4 3 2 1 0 
迭代本身也有可用函数,如;iter()、next()等,这些网络可查询相关用法,这里就不多讲了。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-11-18 15:07:18 | 显示全部楼层
本帖最后由 suchocolate 于 2021-11-18 15:10 编辑

简单理解就是能逐个取值的,比如列表,元组,集合,字符串:
for i in [1,2,3,4,5]:
    print(i)
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-18 15:10:47 | 显示全部楼层
iter(可迭代对象) 能返回迭代器的就是可迭代对象

可迭代对象 需要提供__iter__ 或者__getitem__(0++) 才可以使用iter(可迭代对象)
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-18 16:02:39 | 显示全部楼层
就是可以分?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-18 18:49:42 | 显示全部楼层
一起等
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-18 19:04:02 | 显示全部楼层    本楼为最佳答案   
本帖最后由 傻眼貓咪 于 2021-11-18 19:05 编辑
可迭代对象
可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for 循环中使用的对象。例如字符串也是可迭代的。

Python 的 4 种序列:
列表(list)
集合(set)
元祖(tuple)
字典(dictionary)

Python 字符串(string)

*以上序列和字符串都可迭代(被视为可迭代对象)

例子 1:序列
a = ['a', 'b', 'c', 'd', 'e'] # 列表
b = ('a', 'b', 'c', 'd', 'e') # 元祖
c = {'a', 'b', 'c', 'd', 'e'} # 集合
d = {'one': 'a', 'two': 'b', 'three': 'c', 'four': 'd', 'five': 'e'} # 字典

for i in a:
    print(i, end = " ")
print()

for i in b:
    print(i, end = " ")
print()

for i in c:
    print(i, end = " ")
print()

for i in d.items():
    print(i, end = " ")
print()
输出结果:
a b c d e 
a b c d e 
c d a e b 
('one', 'a') ('two', 'b') ('three', 'c') ('four', 'd') ('five', 'e') 
以上你会发现,除了集合(set)每次不按照顺序输出之外,其他都是有序输出的。这是因为集合(set)是无序的;列表和元祖是有序的,而字典虽说是无序(属於 Hash 的一种)但是当访问时,如 items()、keys()、或values()都会变成有序的(这是因为 Python 3.6 版本之后,字典插入 indices 索引(OderedDict 元素)变得有序。而之前的 2.6 版本字典是乱序的,相关讲解可到 Python 官方自行查询)

例子 2:字符串
arr = "bananaAPPLEflower"

for i in arr:
    print(i, end = " ")
输出结果:
b a n a n a A P P L E f l o w e r 



除了上述以外,也可以自定义迭代器(定义一个迭代对象),如:
def myFunction():
    i = 10
    while i > 0:
        i -= 1
        yield i # yield 用于迭代

a = myFunction()

for i in a:
    print(i, end = " ")
输出结果:
9 8 7 6 5 4 3 2 1 0 
迭代本身也有可用函数,如;iter()、next()等,这些网络可查询相关用法,这里就不多讲了。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-18 19:16:11 | 显示全部楼层
本帖最后由 shatanzongcai 于 2021-11-18 20:45 编辑

在学习可迭代对象之前,你需要先学习类。

在学习可迭代对象时,需要重点区分下面这几个概念:
1.可迭代对象
2.迭代器
3.生成器

可迭代对象,通俗而言,就是能够用for循环将自身元素以某种方式导出的对象,如list,字符串,dict,元组,生成器等都可以用for循环来导出它们所包含的元素。但实际上,这种辨别方法只是从表象和可执行的功能方面来理解可迭代对象,就好像通过鸭子会“嘎嘎”地叫来辨别这是不是鸭子。但我们要通过可迭代对象的定义来认识可迭代对象,也就是从它是什么这一本质出发。

迭代和生成器到底会给我们带来什么好处:假如说,你需要1-10000来运行某种计算,如果你用列表来保存的话,那么对内存的消耗其实非常大。使用迭代,你就可以不用存储每一个数字。因为你看得出来这组数是有规律的,其实每个你需要的数就是在前一个数的基础上+1,那么你只需要在计算之前把这个值+1就可以了。迭代的好处就在于,对于一组有规律的数,你可以用一个非常节省空间和时间的方法来表示出所有的数:第一个数和一个能够推导出后面所有数字的公式。是不是有点像列表推导式?迭代不会为每个数字开辟一个内存空间,所以你不能够在访问第n个数值时,返回去访问第n-1个数值,因为它已经被覆盖掉了。

然后我们来理解一下什么是迭代器:
如果一个对象定义了 __iter__ 和 __next__ 两个方法,它就是一个迭代器。
注意:这里虽然说是一个对象,但是你得先定义一个类才能得到一个对象,我们之后再详细解释。

对于迭代器来说,__iter__ 返回的是它自身 self,__next__ 则是返回迭代器中的下一个值,如果没有值了则抛出一个 StopIteration 的异常。关于这点,你可以想象成一个只进不退的标记位,每次调用 __next__,就会将标记往后移一个元素并返回,直到结束。

给出一个简单的例子:
class Iterator():
    def __init__(self):
        self.count=0 #初始化一个类的属性,一会儿我们还要用到

    def __iter__(self):
        return self #按照定义在__iter__中返回它自己

    def __next__(self):
        self.count+=1 #某种规律,这里我让它每次都+1
        if self.count==3:#停止迭代的条件,如果self.count ==3的话,那么它就会抛出一个专门用来停止迭代的异常,在for循环中,它会被处理掉,所以你不会看到报错
            raise StopIteration
        return self.count#如果没有达到3,就正常返回self.count

它就是一个迭代器啦。类中的这个__iter__(self)其实就相当于,当你要执行迭代相关的操作时,程序会问,你的迭代器是谁。然后该迭代器说我就是迭代器,于是程序就会去找这个迭代器的__next__,看看它的规律是啥。我们来跑一下for循环看看结果。
class Iterator():
    def __init__(self):
        self.count=0 #初始化一个类的属性,一会儿我们还要用到

    def __iter__(self):
        return self #按照定义在__iter__中返回它自己

    def __next__(self):
        self.count+=1 #某种规律,这里我让它每次都+1
        if self.count==3:#停止迭代的条件,如果self.count ==3的话,那么它就会抛出一个专门用来停止迭代的异常,在for循环中,它会被处理掉,所以你不会看到报错
            raise StopIteration
        return self.count#如果没有达到3,就正常返回self.count

for i in Iterator():
    print(i)


#结果是:
1
2

注意:它是个迭代器,我在调用它时,它是个类而不是对象。如果你实例化它其实效果也是一样的。a=Interator(),然后for循环a效果也是一样的,具体原因参考小甲鱼的视频我就不再赘述了。

到这里感觉好像有点头绪了。那么我们来介绍一下什么是可迭代对象。有了迭代器的概念之后,如果一个对象定义了 __iter__ 和方法,返回一个迭代器对象,那么它就是一个可迭代的对象

我们也举一个例子,这里的迭代器就用上面定义的。
class iterable_obj():#可迭代类
    def __iter__(self):#返回一个迭代器对象,我这里直接返回类了,其实效果是一样的,你实例化Iterator然后再把实例塞进去是一样的。
        return Iterator()

a = iterable_obj()#现在a就是一个可迭代对象了

for i in a:
    print(i)

#结果是:
1
2

其实底层原理就是,执行for循环的时候,会对a进行iter操作,这个时候for循环就会问,你的迭代器是谁(关于你的迭代规则,我要去问谁),a说我的迭代器是Iterator(),它的next里面有迭代规则你去找它吧。而for找到Iterator(),然后去找它的next(Iterator()如果在__iter__return的不是self,而是别的迭代器,那就会形成套娃)。至于for循环的底层逻辑我们等会儿再看。

那么你也许会有问题:既然迭代器可以for循环得到我想要的数,我干嘛费那老大劲儿去再构造一个类,然后实例化?这就不得不提到类的封装思想。虽然你迭代器可以实现迭代,很强大。但是这还不够强,我还想在这个可迭代的对象上加上更多的功能。我想让它可以保存每一轮迭代所得到的的值,我还想让它有append,pop等功能,于是我们就把迭代器,pop,append等等功能封装成一个类,也就有了list,字典等等数据类型,他们都是可迭代的类,并且在迭代器的基础上有了更强大的功能。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-18 19:52:54 | 显示全部楼层
本帖最后由 shatanzongcai 于 2021-11-18 20:45 编辑

现在我来造一个自己的range()
class Iterator():
    def __init__(self,num):
        self.count=-1 #因为range是从0开始,而下面next的时候我们是先++再return所以这个必须是-1
        self.num =num #传入迭代结束的那个数
    def __iter__(self):
        return self 

    def __next__(self):
        self.count+=1 
        if self.count==self.num:#如果self.count==self.num它就跳出,这里实现了包头不包尾
            raise StopIteration
        return self.count

class My_range():
    def __init__(self,num):
        self.num = num
    def __iter__(self):
        return Iterator(self.num) #这里的传参千万不要和上面Iterator()的self搞混了,详情也参考小甲鱼的视频

a = My_range(5)#这里需要传参,我们设置它的最大值是5

for i in a:
    print(i)

#结果是:
0
1
2
3
4

我们完美还原了range()这个类。

现在我们来看看之前说的套娃的问题。这回我搞了两个iterator,你能猜到a,b,c结果分别是什么吗。
class Iterator1():
    def __init__(self):
        self.count =-1
    def __iter__(self):
        return self

    def __next__(self):
        self.count+=1
        if self.count==10:#从0-9
            raise StopIteration
        return self.count

class Iterator2():
    def __init__(self):
        self.count =0
    def __iter__(self):
        return Iterator1()

    def __next__(self):
        self.count+=1
        if self.count==5:#从0到5
            raise StopIteration
        return self.count


class iterable_obj1():
    def __iter__(self):
        return Iterator2()

class iterable_obj2():
    def __iter__(self):
    return Iterator2().__iter__()#其实它就等于 return Iterator1()

a = iterable_obj1()
b = Iterator2()
c = Iterator1()
d = iterable_obj2()
for i in a:#b,c,d
    print(i)
#结果是:
0 1 2 3 4 #a

0 1 2 3 4 5 6 7 8 9 #b 

0 1 2 3 4 5 6 7 8 9 #c

0 1 2 3 4 5 6 7 8 9 #d 

你知道为什么吗?实例对象a会去找它的迭代器里的next,a对自己迭代器的迭代器不感兴趣,所以它走的是0-4。但是b是Iterator2的实例,它关心的它的迭代器是啥,注意Iterator2返回的是Iterator1,而非自身,所以for循环问b,你的迭代器是谁,b查看了一下,说自己的迭代器其实是Iterator1,所以它走0-9.那剩下的你就自己分析吧。其实说是套娃,但是它其实只能套一层,它不会一直递归下去,去找迭代器的迭代器的迭代器……的__next__,而是去找该实例(或类)的__iter__返回的迭代器的__next__.


想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-18 20:44:42 | 显示全部楼层
现在我们来看看什么是生成器。

如果一个函数中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。调用函数就是创建了一个生成器(generator)对象

首先我们先使用python自带的生成器类来形成一个生成器。

我们知道列表推导式是
list1 = [x for x in range(10)] #得到的是0-9

那么我们只需要将方括号改成圆括号就是一个生成器了
g = (x for x in range(10))
print(type(g))

#结果是:
<class 'generator'>

使用for循环后:
g = (x for x in range(10))
print(type(g))

for i in g:
    print(i)

#结果:
0-9

我们一会儿再来看for的底层逻辑,先介绍一个内置函数next(),next的作用是:调用生成器来取值。
print(next(g))# 结果是0
print(next(g))#结果是1,它会覆盖掉原来的值,按g的顺序你不可能再取0了

for i in range(11):#我们明知g只有10个元素,但是我们循环打印11次
    print(next(g))

#结果:0-10外加一个报错
  File "C:/Users/DELL/Desktop/生成器.py", line 7, in <module>
    print(next(g))
StopIteration

现在我们来看看如何自己定义一个生成器:
def gen():
    i = 0
    while i<5:
        yield i
        i+=1

a = gen()
print(type(a))

for i in range(5):
    print(next(a))
#结果是:<class 'generator'>
0-4

当然超过范围它仍然还是会报错的。至于整个程序的运算顺序,记住下次迭代时,代码从yield的下一条语句开始执行。你可以开开发工具的debug看一下运行的顺序。


最后我们来看看for循环的底层逻辑:

不过在此之前我们还要再来了解一个python内置函数iter(),它的作用就是让一个可迭代对象变成一个迭代器。
list1 = [x for x in range(5)]

list1_i = iter(list1)
print(type(list1_i))
print(next(list1_i))

#结果:
<class 'list_iterator'>
0

为什么我们有了一个可迭代对象非得再把它变成迭代器呢,因为next()只能够作用在迭代器上,但是list,字典等等不是迭代器!所以如果你真的想用for循环,还真得用iter()把它们变回迭代器。
list1 = [x for x in range(5)]

for i in list1:
    print(i)

list1_i = iter(list1)
while True:
    try:
        print(next(list1_i))
    except StopIteration:
        break

#两次结果都是0-4

总结:迭代器的用途非常广泛,它可以让你形成一个特定的数列,而配合生成器一起使用,能够大大减小数据量过大对内存的压力。如果你需要庞大的数量,又想耗费过多的内存,就用迭代器和生成器。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-1-12 18:43

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表