马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
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
|