python-cookbook学习笔记八 迭代器与生成器一

时间:2021-08-13 16:04:00
迭代器:
假如我们有一个列表 items=[1,2,3].我们要遍历这个列表我们会用下面的方式
For i in items:
  Print i
首先介绍几个概念:容器,可迭代对象,迭代器
容器是一种存储数据的数据结构,容器将所有数据保存在内存中,典型的容器有列表,集合,字典,字符数组等。如items就是一个列表容器。
 
可迭代对象:这个对象是否可迭代。如items也是一个可迭代对象。简单来说如果可以用for循环的对象都称为可迭代对象。如果要判断是否是一个可迭代的对象。可以用print isinstance(items,Iterable)
,如果是True,则证明是一个可迭代对象。
 
迭代器:任何具有__next__()方法的对象都是迭代器。对迭代器调用next方法可以获取下一个值。所以迭代器本质上是一个产生值的工厂。通俗点说就好比我们写C代码遍历数组的作用。
__next__方法可以用下面的C代码来表示:
for(i=0;i<length;i++)
   return list[i]
 
介绍完几个概念,我们重新来看下面的这个代码发生了什么
for i in items:

   
print i
首先items是一个可迭代对象,用for .. in ..的方式其实就是对一个可迭代对象不停调用迭代器的过程。我们将代码改成如下形式来看下:
items=[<span lang="EN-US" style="" color:"="">1,2,3]

L=items.
__iter__()  #返回列表为一个可迭代的对象

print L.next()   #调用next方法来获取下一个值

print L.next()

print L.next()
print L.next()

输出如下,可以看到输出了1,2,3.但是还有一个报错StopIteration。这是因为我们使用了4next方法。但是只有3个元素。在第4个元素的时候。没有可迭代的值了所以抛出了异常。

python-cookbook学习笔记八 迭代器与生成器一

通过这个我们其实用下面的图来解释for的用法。在for..in的结构中其实是将一个列表传入一个迭代器。然后不停的调用next方法输出元素值。当找不到元素值的时候,则抛出异常
python-cookbook学习笔记八 迭代器与生成器一

通过上面代码可以看到items.__iter__()可以返回一个可迭代的对象。现在我们来将一个类变成一个可迭代的对象,也就是重写类的__iter__实现方法。
class Node1():

   
def __init__(self,value):

       
self._value=value

       
self._child=[]

   
def __repr__(self):

       
return 'Node%s' % self._value

   
def add_child(self,node):

       
self._child.append(node)

   
def __iter__(self):

       
return iter(self._child)
下面Node1这个类,首先初始化_value_child两个变量。然后add_child将每个节点加入到_child列表中。最后__iter__返回一个可迭代的对象
下面首先生成root这个根节点,然后生成2个子节点。将这2个子节点加入到root节点中去,最后用for..in的方式调用root.此时
root=Node1(0)

child1=Node1(
1)

child2=Node1(
2)

root.add_child(child1)

root.add_child(child2)

for ch in root:

   
print ch
最后得到结果如下:
python-cookbook学习笔记八 迭代器与生成器一

可以看到最终结果遍历了_child这个列表。在上述的代码中,__iter__将迭代请求传递给了_child属性。
 
生成器:
前面介绍了迭代器的用法,现在介绍一个更简洁的迭代用法,就是生成器。
我们首先来看下这样的一种应用。我们想实现一个函数,这个函数返回值是得到100之内的所有数的平方值,我们根据这个返回值然后对各个值进行处理。一般来说我们会这样实现:
def data_generate(value):

    number=[]

   
for i in range(value):

        num=i*i

        number.append(num)

   
return number
首先定义一个列表,然后将value内的值全部取平方。然后加入到number中去。等所有的数都生成后直接用return返回。这里看上去没啥问题。但是如果我们设置的value10000或者是更大的数。那么对应的列表number也会变得更大。这样就需要更多的内存来存储值。如果这个value足够大,仅仅为了存储这些值就得耗尽所有的内存,哪该怎么办呢。有没有一种方法每当生成一个数的时候,就返回这个值,这样就不需要专门定义一个列表来存储了。
但是return语句每当调用的时候整个函数就停止了。无法满足我们的诉求。不用急,python中的生成器完全我们的需求。而且用法很简单
代码改造成如下
def data_generate(value):

   
for i in range(value):

        num=i*i

       
yield num
如下调用
for i in data_generate(100):

   
print i
 
通过代码可以看到我们去掉了number列表以及return语句。添加了yield num语句。并用调用迭代器的方式调用data_generate函数。最终也达到了我们要的效果。而且最重要的是在函数中我们不需要申请一个占用内存的列表。完美的实现了我们的诉求。
这里介绍yield的用法:yield就是生成器的意思。其实作用就像一个增强版的return语句。每当执行到yield的时候,函数会自动停止,并保存所有的变量。相当于执行了一个中断,然后会返回一个当前的值。然后代码从yield num的下一条语句继续执行。
我们来看下代码的单步执行结果:
执行第一次循环的时候,i=0,num=0,value=100
python-cookbook学习笔记八 迭代器与生成器一

调用yield num后返回0值,得到输出结果如下
python-cookbook学习笔记八 迭代器与生成器一

接着进入第二次循环,i=1,在上一次的基础上加1num=1
python-cookbook学习笔记八 迭代器与生成器一

最终输出结果如下:
python-cookbook学习笔记八 迭代器与生成器一


进入第三次循环,i=2,num=4.
python-cookbook学习笔记八 迭代器与生成器一

输出结果如下
python-cookbook学习笔记八 迭代器与生成器一

从上面的单步调用的结果可以很直观的看出yield的用法
其实yield就是一个增强版的迭代器。我们可以也将代码改成如下。可以看到ret是一个迭代器,然后我们不停的调用next就可以得到每次每次调用的值。
ret=data_generate(100)print ret.next()print ret.next()print ret.next()
得到输出结果如下:
python-cookbook学习笔记八 迭代器与生成器一