再python中,当对象被索引的时候会去调用__getitem__()和__setitem__()的魔法方法,主要有三个作用,我们依次用代码来演示。
class C:
def __getitem__(self,index): #定义__getitem__()方法
print(index) #打印索引值
c = C()
c[2]
# 2
class C:
def __getitem__(self,index):
print(index)
c = C()
c[1:2]
# slice(1, 2, None)
当进行切片索引的时候,会打印出 slice(1, 2, None);对于slice(),它是一个BIF函数,切片操作相当于是它的一个语法糖。
BIF:(built-in-functions)顾名思义,就是Erlang【一种大规模并行处理环境的语言】内建函数。通常用来完成那些无法用Erlang完成的任务,比如将列表转换为元组,或者获取当前的日期和时间,完成这些操作的函数,我们称之为BIF函数。
s = "Hello World"
s[0:5]
# 'Hello'
s[slice(0,5)]
#'Hello'
s[6:]
# 'World'
s[slice(6,None)]
# 'World'
s[::2]
# 'HloWrd'
s[slice(None,None,2)]
# 'HloWrd'
在上述代码中,我们用普通的切片操作和slice()函数进行比较,可以得到相同的结果,但是要注意的是,普通切片中没有写的位置,在slice()方法中要使用 'None' 来声明。
当为索引或者切片赋值的时候,就会被__setitem__()方法拦截。
class C:
def __init__(self,data):
self.data = data
def __getitem__(self,index):
return self.data[index]
def __setitem__(self,index,value):
self.data[index] = value
c = C([1,2,3,4,5])
c[2]
# 3
#单下标赋值
c[2] = 2
c[2]
# 2
# 切片赋值
c[0:5] = [5,4,3,2,1]
c[:]
#[5, 4, 3, 2, 1]
对于for循环,每次都会从可迭代对象中获取数据,这样的操作也会触发__getitem__()方法。
class C:
def __init__(self,data):
self.data = data
def __getitem__(self,index):
return self.data[index] * 2 #使得到的数据增加一倍
c = C([1,2,3,4,5])
for i in c: #通过for循环进行遍历
print(i,end = " ")
# 2 4 6 8 10
我们得到了列表中扩大一倍的数据,说明使用for循环触发了__getitem__()方法,由此说明,__getitem__()会拦截与获取有关的操作。
对于for循环去访问__getitem__()是Python退而求其次的一种做法,真正实现对可迭代对象的魔法方法是__iter__()和__next__()。【当没有找到__iter__()和__next__()方法的时候,才回去寻找__getitem__()方法】
如果一个对象定义了__iter__()方法,那么他就是一个可迭代对象,如果一个可迭代对象定义了__next__()魔法方法,那么他就是一个迭代器。
比如列表,列表是一个可迭代对象,但是因为没有__next__()方法,所以他不是一个迭代器。
x = [1,2,3,4,5]
dir(x) #可以看到,列表中有很多中方法,但是就是没有__next__()
'''
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__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(x) #调用next的时候会报错
'''
Traceback (most recent call last):
File "", line 1, in
next(x)
TypeError: 'list' object is not an iterator
'''
当我们使用迭代工具for循环来操作时,首先会将可迭代对象放入内置的iter()中,以此得到一个相应的迭代器,继而获得所需的__next__()魔法方法。之后利用__next__()进行真正的迭代操作。
x = [1,2,3,4,5]
a = iter(x) #将可迭代对象放入内置的 iter()中 获取迭代器
while True:
try:
i = a.__next__() #通过__next__()进行迭代操作
except StopIteration: #当循环到最后结束程序
break
print(i,end = " ")
# 1 2 3 4 5
根据上面描述的信息,我们可以创建一个属于自己的迭代器:
class DieDai:
def __init__(self,start,stop):
self.value = start - 1
#对于为什么减一,是因为在使用for循环的时候,会从下标为0开始访问,不想遗漏列表中第一个数据。
self.stop = stop
def __iter__(self):
return self #本身就是一个迭代器,所以直接返回自身即可
def __next__(self):
if self.value == self.stop: #当迭代到最后一个元素时,停止
raise StopIteration #一但执行了raise语句,后面的语句就不再执行
self.value += 1
return self.value * 2
d = DieDai(1,5)
for i in d:
print(i,end=' ')
# 2 4 6 8 10