前言
python入门时学的是廖海峰老师的课件,对于迭代器和生成器这两章节,我以为我懂了,结果被问的时候却不知道如何组织语言去描述这个东西,男朋友说,如果你说不清楚,那一定是不够了解,或者说了解得不够深,只停留在表面。虽然刚开始被点评时会很排斥,但是冷静过后,发现自己可能的确是了解得不够深入。所以打算完全用自己的语言去记录和描述目前对这两个知识点的理解。也希望能够得到更多人的点评,让自己更熟悉这块。
可迭代和可迭代对象
在认识迭代器之前,我们先来了解下什么是可迭代和可迭代对象。我们都知道,给定一个list或者一个tuple,我们可以通过for循环去遍历它的元素,这种能够通过当前元素知道下一个元素是哪个,并且可以不断地遍历出下一个元素的方式称为可迭代,它特指元素能够被遍历获取的特性
a = ['11','aa',22,33]
for i in a:
print(i)
# 输出结果 11 aa 22 33
可迭代对象是可以遍历的对象,拥有可迭代特性,内部实现了__iter__魔法方法,凡是实现了__iter__魔法方法的类实例都是可迭代对象。
python中的数据类型有很多,那我们怎么才能知道一个对象是否是可迭代对象呢?collection.abc中提供了一个Iterable类型来判断
from collections.abc import Iterable
print(isinstance([1,23], Iterable))
print(isinstance((1,2,3), Iterable))
print(isinstance('123', Iterable))
print(isinstance({'aa':11,"bb":22}, Iterable))
# 输出结果 True True True True
可迭代对象不能独立使用用for…in…循环遍历(需要结合迭代器),但是凡是可以通过for…in…循环遍历的对象,都是可迭代对象。
class Mytest_iter(object):
def __init__(self):
self.num_list = []
def add(self, num):
self.num_list.append(num)
# def __iter__(self):
# pass
# return self.num_list
test = Mytest_iter()
test.add(1)
test.add(2)
test.add(3)
# 返回true,因为内部实现了__iter__()方法,所以是可迭代对象
print(isinstance(test, Iterable))
print('*'*30)
# 报错,因为这个可迭代对象没有实现了next()方法的迭代器
for num in test:
print(num)
# 结果:
# True
# ******************************
# Traceback (most recent call last):
# File "D:/Workspace/projects/unitest/melotAutoTest/test/test02.py", line 82, in <module>
# for num in test:
# TypeError: iter() returned non-iterator of type 'NoneType'
只要是可迭代对象,不管它有没有下标,都可以被迭代,python中常用的的可迭代对象有list、tuple、str、dict等,整型和浮点型不是可迭代对象。
str = 'abh'
dicts = {'aa':11,'bb':'gg'}
for i in str:
print(i)
print('*'*30)
for key,value in dicts:
print(key+'-->'+value)
print('*'*30)
for v in 123:
print(i)
# 结果
# a
# b
# h
# ******************************
# a-->a
# b-->b
# ******************************
# Traceback (most recent call last):
# File "D:/Workspace/projects/unitest/melotAutoTest/test/test02.py", line 98, in <module>
# for v in 123:
# TypeError: 'int' object is not iterable
#
# Process finished with exit code 1
到这里,你有没有很疑问,为什么list、tuple这类可迭代对象可以通过for…in…进行遍历,可以从当前元素就能够知道下一个元素?如果我们希望我们自己定义的可迭代对象也能够通过for…in…进行遍历,需要怎么做?这个时候就要提到迭代器。
迭代器
list、tuple这类可迭代对象可以通过for…in…进行遍历,是因为它内部有个称为迭代器的东西能够帮助它记住当前遍历的位置,使它每次都能知道下一个元素是在哪。迭代器内部实现__iter__和__next__两个魔法方法,它可以被next()函数调用并不断返回下一个对象,表示一个惰性计算的序列,当使用next方法作用于迭代器时,它会直接去调用迭代器的__next__魔法函数
我们可以通过iter()方法去获取一个可迭代对象的迭代器(前提是该可迭代对象的__iter__魔法函数返回了一个迭代器,迭代器中实现了__next__魔法函数),通过collections.abc 下的 Iterator去判断一个对象是否为迭代器
lists = [1,2,3,4]
str = 'abg'
dicts = {'aa':11,'bb':'gg'}
list_iterator = iter(lists)
str_iterator = iter(str)
dict_iterator = iter(dicts)
print(list_iterator)
print(isinstance(list_iterator,Iterator))
print(str_iterator)
print(isinstance(str_iterator,Iterator))
print(dict_iterator)
print(isinstance(dict_iterator,Iterator))
# 结果
# <list_iterator object at 0x000002859DF9EB88>
# True
# <str_iterator object at 0x000002859DF9EBC8>
# True
# <dict_keyiterator object at 0x000002859DF4B188>
# True
for…in…内部循环遍历的实质是:
1、通过iter获取到可迭代对象的迭代器 2、使用next方法调用获取到的迭代器 3、处理for循环内部操作 4、重复循环执行2-3步骤 5、当可迭代对象内部全部元素循环获取完,再调用next方法,抛出StopIteration错误,处理错误 |
---|
自定义可迭代对象和迭代器
我们可以通过自定义可迭代对象和迭代器,使可迭代对象能够用于for…in…循环迭代中
首先通过编写实现了__iter__魔法方法的类来实现可迭代对象,并在__iter__中返回迭代器
class Mytest_iter(object):
def __init__(self):
self.num_list = []
def add(self, num):
self.num_list.append(num)
# def __getitem__(self, item):
# print(self.num_list)
# return self.num_list[item]
def __iter__(self):
return Mytest_iterator(self)
然后具体实现一个迭代器(在内部实现__iter__和__next__方法)
class Mytest_iterator:
def __init__(self,mytest_iter):
self.num_list = mytest_iter.num_list
self.i = 0
def __iter__(self):
return self
def __next__(self):
if self.i < len(self.num_list):
item = self.num_list[self.i]
self.i += 1
return item
else:
# 模拟for循环的话需要直接抛出异常,因为for循环中对这个异常进行了处理,如果我们自己定义处理,将会和for循环中的处理不一致,从而导致一些异常
raise StopIteration
# 如果不将i重置为0,对一个可迭代对象重复调用for循环,将不会有结果,因为i已经达到上限
self.i = 0
使用
test = Mytest_iter()
mytest_iterator = iter(test)
test.add(1)
test.add(2)
test.add(3)
print(isinstance(test, Iterable))
print(isinstance(mytest_iterator,Iterator))
print('*'*30)
for num in test:
print(num)
print('*'*30)
for num in test:
print(num)
print('*'*30)
print(next(mytest_iterator))
print(next(mytest_iterator))
print(next(mytest_iterator))
# 会抛出StopIteration异常
print(next(mytest_iterator))
# 结果:
# True
# True
# ******************************
# 1
# 2
# 3
# ******************************
# 1
# 2
# 3
# ******************************
# Traceback (most recent call last):
# File "D:/Workspace/projects/unitest/melotAutoTest/test/test02.py", line 127, in <module>
# next(mytest_iterator)
# File "D:/Workspace/projects/unitest/melotAutoTest/test/test02.py", line 97, in __next__
# raise StopIteration
# StopIteration
思考
既然实现了__iter__魔法方法的对象是可迭代对象,而迭代器既实现了__iter__方法,又实现了__next__方法,那说明迭代器它就是一种特殊的可迭代对象,既然是这样,那迭代器中的__iter__只需要返回自己,就可以同时作为一个可迭代对象和迭代器使用。那么上面的代码可以整合升级
class Mytest_iterpre(object):
def __init__(self):
self.num_list = []
self.i = 0
def add(self, num):
self.num_list.append(num)
def __iter__(self):
return self
def __next__(self):
if self.i < len(self.num_list):
item = self.num_list[self.i]
self.i += 1
return item
else:
self.i = 0
raise StopIteration
test = Mytest_iterpre()
mytest_iterator = iter(test)
test.add(1)
test.add(2)
test.add(3)
print(isinstance(test, Iterable))
print(isinstance(mytest_iterator,Iterator))
print('*'*30)
for num in test:
print(num)
print('*'*30)
for num in test:
print(num)
print('*'*30)
next(mytest_iterator)
next(mytest_iterator)
next(mytest_iterator)
next(mytest_iterator)
# 结果:
# True
# True
# ******************************
# 1
# 2
# 3
# ******************************
# 1
# 2
# 3
# ******************************
# Traceback (most recent call last):
# File "D:/Workspace/projects/unitest/melotAutoTest/test/test02.py", line 148, in <module>
# next(mytest_iterator)
# File "D:/Workspace/projects/unitest/melotAutoTest/test/test02.py", line 121, in __next__
# raise StopIteration
# StopIteration
迭代器是特殊的可迭代对象,它需要具有可迭代的特性,即__iter__方法。需要能够记住返回下一个元素,所以需要实现__next__方法。如果一个对象A只实现了__next__方法,需要配合可迭代对象B,通过可迭代对象B的__iter__返回A对象,才能实现for循环。
getitem
借着这个劲,将__getitem__这个魔法函数也讲下。为什么在这里提到这个函数,是因为在我们使用for…in…这种迭代循环一个对象时,它会先去尝试调用这个对象的__iter__方法,如果存在这个方法,则执行该方法,如果不存在,它会改用__getitem__机制,通过使用下标获取类中的对应元素值,或者把实例变成可迭代器。所以说,如果对象实现了__getitem__方法,也是可以使用for…in…对其进行遍历的
class Mytest_iterpre(object):
def __init__(self):
self.num_list = []
self.i = 0
def add(self, num):
self.num_list.append(num)
def __getitem__(self, item):
return self.num_list[item]
test = Mytest_iterpre()
mytest_iterator = iter(test)
test.add(1)
test.add(2)
test.add(3)
# 通过实例[索引]直接获取值
print(test[0])
print(isinstance(test, Iterable))
print(isinstance(mytest_iterator,Iterator))
print('*'*30)
for num in test:
print(num)
print('*'*30)
for num in test:
print(num)
print('*'*30)
next(mytest_iterator)
next(mytest_iterator)
next(mytest_iterator)
next(mytest_iterator)
# 结果:
# 1
# False
# True
# ******************************
# 1
# 2
# 3
# ******************************
# 1
# 2
# 3
# ******************************
# Traceback (most recent call last):
# File "D:/Workspace/projects/unitest/melotAutoTest/test/test02.py", line 149, in <module>
# next(mytest_iterator)
# StopIteration
只要实现了__getitem__魔法方法,我们可以通过 对象名[索引] 的方式去访问实例对象中的属性, 而且这个对象还会被当做迭代器,可用于for循环中。
当__iter__和__getitem__同时存在时,使用for循环,会直接调用__iter__,使用 实例名[索引] 会直接调用__getitem__
生成器
当我们想要生成的数据个数不确定,并且生成数据的方法可能需要变化时,就可以使用到生成器。比如我们平时使用的列表生成式,如果我们要通过列表生成式生成含有个别元素的列表时,它很实用,但如果想要生成大量元素,而且又不确定要使用到多少元素时,一次性生成大量的数据,需要耗费比较长的时间,而且会造成空间的占用浪费。生成器是一种一边循环一边计算下个元素的东西,他内部存储的不是具体的数据值,而是计算出这些数据的方法。它既然能够计算出下一个数据,那说明它也能够记住上一个数据,所以它也是一种迭代器,只不过是通过另一种不实现__iter__和__next__的迭代器,它也是通过next循环遍历出下一个对象。我们来看看它是怎么实现的。
生成器有两种实现方式,一种是对于生成数据计算方式比较简单的方法,它是直接将列表生成式的[]转变为();另一种是对于计算方法比较复杂的方法,它是在内部使用yield,将对象转变为生成器,yield后面是需要返回的数据。当我们使用next对它进行调用时,它返回当前遍历的元素,当访问完所有的元素时,再次使用next函数调用,它也会抛出StopIteration异常
a = (x*x for x in range(1,3))
print(a)
print(next(a))
print(next(a))
print(next(a))
# 结果:
# <generator object <genexpr> at 0x000001CE9ECD1F48>
# 1
# 4
# Traceback (most recent call last):
# File "D:/softCode/Python/projects/pythonStudy/test02.py", line 19, in <module>
# print(next(a))
# StopIteration
实现斐波拉契数列:1,1,2,3,5,8,13,21…
def fib_generator():
num1 = 1
num2 = 1
while True:
temp_num = num1
num1,num2 = num2,num1+num2
yield temp_num
return 'my test return'
fib = fib_generator()
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
# 结果
# 1
# 1
# 2
# 3
# 5
当我们第一次调用next函数的时候,它从fib_generator()方法的顶部开始执行,直到遇到yield,执行yield语句后,返回yield后面的数据,然后停止运行,等到下次再次调用next函数时,它会继续从上次停止运行的地方,接着运行,直到再次遇到yield,返回数据,停止运行,当返回的数据超出界限时,再次调用next函数,也会抛出StopIteration异常。
如果在生成器中包含return语句,则在调用next函数后,遇到return语句时,会直接抛出异常StopIteration,return返回的数据不会直接获取到,而是在StopIteration中的res中
def fib_generator():
num1 = 1
num2 = 1
while True:
temp_num = num1
num1,num2 = num2,num1+num2
yield temp_num
return 'my test return'
try:
fib = fib_generator()
print(next(fib))
print(next(fib))
except StopIteration as e:
print(e.value)
# 结果
# 1
# my test return
此时我们通过生成器解决了无法确定需要生成的数据个数的问题,那还有在生成数据的过程中可能需要修改数据的计算方法应该怎么做到呢?这个时候就需要使用到send()函数,这个函数可以在上次yield语句断点处传入需要接收的参数,从而改变相应的算法
下面是计算一个2x+3y的函数:
def generator_cacul():
a = 2
b = 3
x = 1
y = 1
for x in range(1,3):
for y in range(1,3):
res = a*x +b*y
a,b = yield res
cacul = generator_cacul()
print(cacul.send(None))
print(cacul.send((1,1)))
# 结果
# 5
# 3
因为send函数需要从上次断点处执行,所以在调用send函数前,必须要先执行一次next函数或者执行一次send(None),send(None)只能执行一次,无法代替next那样一直执行,send函数必须携带至少一个参数,否则会抛出异常