QQ登录

只需一步,快速开始

开启左侧

Python学习第六十三天—类和对象12-索引、切片、迭代协议

[复制链接]
15271953841 发表于 2024-3-27 12:06:32 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

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

x
索引、切法和迭代协议
第61课时最后我们学习了__index__()魔法方法,当时大家以为当对象发生索引操作的时候,就会触发该方法。但是通过学习后,我们发现真相是:当对象作为索引值和参数的时候,才会去调用这个__index__()魔法方法,相当搞笑,本来以为自己是个主角,结果到头来却发现自己是小丑。
当对象被索引的时候,Python究竟会怎么做?
事实上,Python会去调用__getitem__(self,index)的魔法方法,这个魔法方法很厉害,它既能响应单个下标索引操作,又能支持代表范围切片索引的方式。
Python 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.
>>>class C:
    def __getitem__(self,index):
        print(index)

        
>>>c = C()
>>>c[2]                 (当小c被索引的时候,这里索引值传入一个2,它就打印了2)
2
>>>c[2:8]                (传入的是一个切片的操作,相关内容请复习第13课)
slice(2, 8, None)         (打印的是一个叫slice的函数,它是BIF函数,切片操作就相当于它的一个语法糖)
>>>s = "I love little sister."
>>>s[2:6]               
'love'
>>>s[slice(2,6)]              (使用slice()函数调用形式,得到的结果与直接切片的结果一样)
'love'
>>>s[7:]
'little sister.'
>>>s[slice(7,None)]            (要加入一个None,充当结尾下标数,只有开头的7不行)
'little sister.'
>>>s[::4]
'Ivies.'
>>>s[slice(None,None,4)]         (要用None表示开头和结尾的下标数)
'Ivies.'
那么相反的,为索引或切片赋值操作的时候,就会被__setitem__()魔法方法所拦截。
>>>class D:
    def __init__(self,data):
        self.data = data            (将这个data数据存放到对象的属性里面去)
    def __getitem__(self,index):
        return self.data[index]         (通过去调用这个data属性的index来获取这个数据,要把它返回)
    def __setitem__(self,index,value):
        self.data[index] = value

        
>>>d = D([1,2,3,4,5])
>>>d.data
[1, 2, 3, 4, 5]
>>>d.__dict__
{'data': [1, 2, 3, 4, 5]}
>>>d[1]
2
>>>d[1] = 1           (修改)
>>>d[1]               (改过来了)
1
>>>d.data
[1, 1, 3, 4, 5]
>>>d[2:4] = [2,3]             (如果是切片的方式也没问题)
>>>d.data
[1, 1, 2, 3, 5]
>>>d[:]
[1, 1, 2, 3, 5]
>>>d.__dict__
{'data': [1, 1, 2, 3, 5]}
提示(提醒,强调)一下:这里的__getitem__()不仅仅是拦截了索引和切片的操作,其实与获取相关的操作也会被拦截,比如说在for循环语句中,每一次循环,Python都要从可迭代对象中去拿点什么东西出来,那么这样的操作也会触发__getitem__()魔法方法。
>>>class D:
    def __init__(self,data):
        self.data = data
    def __getitem__(self,index):
        return self.data[index] * 2    (*2,做一点小手脚,当拿到的数据乘以2,我们就知道是__getitem__()起作用了。)

   
>>>d = D([1,2,3,4,5])    (给到的数据)
>>>for i in d:
    print(i,end=" ")

   
2 4 6 8 10              (拿到的数据)
拿到的数据与上面给到的数据不同,被乘以了2,说明for语句会去访问__getitem__()这个魔法方法。
闻道有先后,术业有专攻。
对于索引和切片,我们用getitem和setitem这两种方法无庸置疑,不过呢,像for语句去访问getitem这种操作,Python是退而求其次的一种作法而已,不得已的做法。因为Python有提供更好的拦截方案:就是针对可迭代对象的魔法方法__iter__(self)和__next__(self),它们对应的BIF函数就是iter()函数和next()函数,以前提到这两个函数是在讲可迭代对象和迭代器(参看第29课时),事实上,根据Python的迭代协议来说,如果一个对象定义了__iter__()魔法方法,那么它就是一个可迭代对象。如果一个可迭代对象定义了__next__()魔法方法,那么它就是一个迭代器,比如说列表是一个可迭代对象,不是迭代器,因为它没有__next__()魔法方法。
>>>x = [1,2,3,4,5]
>>>dir(x)
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
里面就是没有__next__,所以给它调用next()函数的时候,它就会报错。
>>>next(x)
Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    next(x)
TypeError: 'list' object is not an iterator
报错原因:Python会报错,说列表不是一个迭代器。但是可迭代对象是定义了__iter__()魔法方法的,只有调用了这个方法,就可以得到相应的迭代器对象,所以当我们去使用迭代工具,比如说,for循环语句,对应于一个可迭代对象进行操作的时候,像这个样子:
>>>for i in x:
    print(i,end=" ")

   
1 2 3 4 5
For语句作了第一步操作呢,必然是先将对象传入内置函数iter()中(参考第29课),并由此拿到一个相应的迭代器,因为只有拿到这个迭代器,才能拥有所需要的__next__()魔法方法。
然后第二步就是利用这个__next__()魔法方法进行真正的迭代操作,下面我们模拟一下for循环语句的执行流程:
>>>_ = iter(x)            (这里我们调用iter,获取x列表的迭代器对象,放到临时变量“_”里面去,就起一个下划线的临时变量名字)
>>>while True:
    try:
        i = _.__next__()
    except StopIteration:         (当迭代器去到尽头时,就会抛出一个StopIteration异常)
        break
    print(i,end=" ")             (模拟for循环语句)

   
1 2 3 4 5
得到一样的结果,使用for循环语句就相当于开启了自动挡,而我们现在这么做(上面的代码)是手动挡模式。
那么现在我们知道了原理,我们可以自己创造一个迭代器对象:
>>>class Double:
    def __init__(self,start,stop):      (希望Double能够将start到stop之间所有的整数给它去乘以2,然后返回出来)
        self.value = start - 1
        self.stop = stop
    def __iter__(self):               (希望Double是一个迭代器,首先得定义__iter__()魔法方法,拥有__iter__()方法,它就是一个可迭代对象了)
        return self                  (返回一个迭代器,因为它自己就是一个迭代器,直接返回它自己就行了)
    def __next__(self):                (迭代器除了拥有__iter__()方法,还必须定义一个__next__()的魔法方法,因为__next__()才是实现迭代真正操作的)
        if self.value == self.stop:
            raise StopIteration
        self.value += 1
        return self.value * 2

   
>>>d = Double(1,5)
>>>for i in d:
    print(i,end=" ")

   
2 4 6 8 10

客服热线
400-1234-888 周一至周日:09:00 - 21:00
公司地址:襄阳市樊城区长虹路现代城5号楼188

创客帮MAKER.BAND青少年创客创意社区是一个融教育、科技、体育资讯为一体的综合服务平台,专注于教育创新、专注于科技体育、专注于教育资讯。

Powered by Discuz! X3.4 © 2001-2013 Comsenz Inc.

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