迭代器:
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象,是python中的一种特殊的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束,并且迭代器只能往前不会退后。
1.可迭代对象:
如果我们想访问列表、元组、集合、字典、字符串等类型里面的数据,可以使用for…in… 循环依次访问。这个过程叫做遍历,也叫迭代。我们把列表、元组、集合、字典、字符串等这种可以使用for循环进行遍历的对象称为可迭代对象。
但是并不是所有的数据类型都可以放到 for…in… 的语句中然后取出里面的数据。看下面的一个例子:
for i in 10: # 10是一个数字类型, 即 int类型
print(i)
结果它会给我们报错,说 int 类型不是一个可以迭代的对象。也就是说 int 用不了for循环。
TypeError: 'int' object is not iterable
那问题来了:什么样的类型能用for循环?什么样的又不能用for循环?
如果说 in 后面的是一个可以迭代的对象,那就能用for循环,否则用不了。像数字类型、带小数点的都用不了。
问题又来了: 怎么判断一个对象是不是可以迭代的?
我们先导入一个模块
from collections import Iterable
Iterable 是一个可迭代的类,我们想要判断某一个东西是否是可以迭代的,只需要判断这个东西是不是 Iterable 的一个子类。我们通过isinstance(对象, 类) , 它的特点是可以判断某一个东西是否是某一个东西创建出来的。例如:
print(isinstance(list(), Iterable)) # 列表
print(isinstance(tuple(), Iterable)) # 元组
print(isinstance(set(), Iterable)) # 集合
print(isinstance(dict(), Iterable)) # 字典
print(isinstance(str(), Iterable)) # 字符串
---->结果<----
True
True
True
True
True
他们的返回结果是True,说明他们都是可以迭代的对象。那我们再来看下面的例子:
print(isinstance(int, Iterable)) # 数字
print(isinstance(float, Iterable)) # 浮点数
---->结果<----
False
False
返回的结果是 False, 说明他俩和 Iterable 没有关系, 他们不是可以迭代的对象。
2.自己定义一个类来实现可迭代
我们首先来定义一个类:
class AddName(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
name = AddName()
name.add("老大")
name.add("老二")
name.add("老三")
定义了一个Name类, 并实例化一个对象,这个类里面有个列表,我往列表里里面放了三个名字。 那我们接下来的目的就是想看一下我们定义的这个类能不能通过 for 循环把里面的名字给取出来。 我们加入下面的代码并执行(因为这个代码下面会多次用到,我把它标注成 1 号代码):
for i in name:
print(names)
结果:
TypeError: 'AddName' object is not iterable
他说我们定义的这个类 is not iterable, 也就是说我们定义的这个普通的类和 Iterable 没有关系,它实现不了。
那我们铁了心就是想要用 for 循环给他实现该咋办?
我们在定义的类里面加入一个额外的方法,先不管里面写什么:
def __iter__(self):
pass
那我们现在再来执行 1号代码,看一下结果:
TypeError: iter() returned non-iterator of type 'NoneType'
结果它还是会报错,但是报的错误不一样了。那接下里我们验证一个东西,:
print("判断AddName是否是可以迭代的对象:", isinstance(AddName(), Iterable))
结果--->
判断AddName是否是可以迭代的对象: True
我们在定义的类里面不加入 def iter(self): 这个方法再来验证一下并打印结果。(不知道为啥,加粗后下滑线显示不出来了)
判断AddName是否是可以迭代的对象: False
那我们现在就非常明了了,如果想要一个对象称为可以迭代的对象,即可以使用 for ,我们就必须在这个类里面实现 def iter(self): 方法。现在已经满足了 for 循环的一个基本条件了 即 in 后面跟一个可迭代对象。
但是我们仅仅只是实现了这一个方法还不够,因为上面已经给我们报了另外一个错误:
TypeError: iter() returned non-iterator of type 'NoneType'
要解决这个问题,我们需要 def iter(self): 这个方法返回一个–>>>具有 def iter(self): 方法 和 def next(self): 方法的 对象的引用。现在它是什么东西都没有返回,为None。那我们接下来让他返回一点东西。我们在定义一个类,这个类 具有 iter方法和 next 方法。有iter方法和next方法,那他就是一个迭代器。
class NameIterable(object):
"""这是一个迭代器, 因为他里面有 iter 和 next 方法, 二者缺一不可。"""
def __iter__(self):
pass
def __next__(self):
pass
我们再更改一下AddName类里面的 iter 方法:
def __iter__(self):
return NameIterable()
# NameIterable()表示创建了一个实例对象, 并让iter方法返回这个实例对象的引用。
# 得到引用后,for循环会自动调用他里面的next方法,调用一次,取出里面的一个值。
我在理一遍这个流程: 只要再定义的类里面加入 iter 这个方法, 并且它返回一个对象的引用, 这个对象必须具有 iter和next方法,那么理论上讲,我们定义的这个类就可以使用 for 循环了。
那我们接下来再来捋一下当我们执行 for 循环的时候到底干了一件什么事:
for tmp in xxx:
pass
第一步:先判断xxx是否是可以迭代。
第二步:在第一步成立的前提下,自动调用 iter 函数,得到xxx对象的__iter__方法返回值。
第三步:__iter__方法的返回值如果是一个迭代器,那么就可以实现for循环了。
for 循环通过这个迭代器里面的next函数来取值。取出来什么就把什么给tmp
我们再来看1号代码 ,可以看到,我们明明是把AddName对象的引用即name放到in后面去了,但是将来在最终实现的时候,iter方法返回的是谁的引用,那么for循环就会调用谁里面的next方法。
我们再来验证一下下面的代码:
from collections import Iterator # Iterator可以判断是不是一个迭代器
print("判断AddName是否是可以迭代的对象:", isinstance(AddName(), Iterable))
print("判断NameIterable是否是迭代器:",isinstance(NameIterable(), Iterator))
-->
判断AddName是否是可以迭代的对象: True
判断NameIterable是否是迭代器:True
现在我们已经非常清楚for循环到底是怎么实现的了。下面我们就来给NameIterable的next函数增加返回值。我把完整的代码给出来
class AddName(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
def __iter__(self):
return NameIterable(self)
class NameIterable(object):
def __init__(self, obj):
self.obj = obj
self.index = 0 # 下标
def __iter__(self):
pass
def __next__(self):
if self.index < len(self.obj.names):
result = self.obj.names[self.index]
self.index += 1
return result
else:
raise StopIteration
name = AddName()
name.add("老大")
name.add("老二")
name.add("老三")
for i in name:
print(i)
我们最终的目的是想取出老大老二老三,但是他们都存在name里面的列表中,因此如果NameIterable想要取出list里面的值,我们把name本身当作参数传给NameIterable,即:(这图可能有点丑!!!)
最后再来说明一下NameIterable的next函数:
如果我们不加这段代码:
else:
raise StopIteration
先把老大老二老三打印出来,接下来会一直打印None。加上之后它会自动判断有没有异常,只要有异常自动就停了。(可以自己试一下加上与不加上的区别,自己体验一下)
当然第二个类可以不用写,修改后是这样的(这里我不作解释了):
class AddName(object):
def __init__(self):
self.names = list()
self.index = 0
def add(self, name):
self.names.append(name)
def __iter__(self):
return self
def __next__(self):
if self.index < len(self.names):
result = self.names[self.index]
self.index += 1
return result
else:
raise StopIteration
name = AddName()
name.add("老大")
name.add("老二")
name.add("老三")
for i in name:
print(i)
补充一点: 如果一个对象是迭代器,它一定可以迭代(修改之后的代码)。反之就不一定了(第一段代码)
以上就是 迭代器迭代实现的整个过程了,说了这么多得出来的一个结论就是原来可以用for循环顶替它(哈哈哈哈哈哈哈…,确实是这么回事,但是这不是重点)。重点来了---->
3.迭代器的应用:
举个例子,如果我们想要生成100以内的数字我们可以这样写:
nums = [i for i in range(10)]
如果我们想要生成100000或者100000000000000或者更多呢?难道我们要写…算了不写了。在python2中,总之你用range(1000000000000),它会生成一个庞大的列表,占用很大的内存空间,这样做不太合适。但是在python3中这中情况就不存在了,在写range(10000000000000),它就相当于python2中的xrange(可自行百度)。
那我们看一下用到迭代器会怎么样。它的一个优点就是可以实现for循环,可以通过for循环取出里面的数据,但是它里面不是生成数据的结果,而是生成数据的方法。 打个比方:你住在很高的楼城,而且是还没有外卖的时代,想吃饭的时候不想往下跑,现在你有两种方案:1、买上一大堆食物放到冰箱里,什么时候吃什么时候取。2、自己学会做饭,什么时候吃什么时候自己做。那迭代器就好比第二种方式,什么时候用什么时候取,占用极小的内存空间。
总结: 我们可以通过迭代器,按照一定过的规律去生成一些数据,而不是将所有要迭代的数据一次性缓存下来,不用再去依赖某一个集合了,节省了很大的内存空间。
后续:
关于迭代器的知识就先说到这里,如果有时间的话,我会把生成器和装饰器的实现过程详细写出来,也算是为了以后自己如果忘记的话,可以让自己可以更快的回忆起来吧。还是那句话,有不足之处请各位帮忙斧正。
撒花–完结。