【Python提高】迭代器,以及对生成器(Generator)以及yield关键字的理解

要讲生成器,不得不说的一个东西就是迭代器了,

所以先说一下迭代器。

迭代器

迭代,很简单,就是重复一个操作多次嘛。迭代器(Iterator)是一个对象,它的工作是遍历并选择序列中的对象,它提供了一种访问一个容器(container)对象中的各个元素,而又不必暴露该对象内部细节的方法。通过迭代器,开发人员不需要了解容器底层的结构,就可以实现对容器的遍历。由于创建迭代器的代价小,因此迭代器通常被称为轻量级的容器。

初识:

在python中,没有内置迭代器类型的对象,但是可以通过内置函数iter将str,tuple,list,dict,set等类型转换成一个迭代器对象。

>>>lst = [1,2,3]
>>>next(lst)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: 'list' object is not an iterator
>>>next(iter(lst))
1
# 
>>>lst_iter = iter(lst)
>>>next(lst_iter)
1
>>>next(lst_iter)
2
>>>next(lst_iter)
3
>>>next(lst_iter)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

当然了,通过不断的调用next(iterator)方法来获取下一个值,这样其实很不方便的,python提供了更为简洁的方法,即for循环。for循环每执行一次即相当于调用了一次next(iterator)方法,直到捕获到StopIteration异常退出循环。

>>>lst_iter = iter(lst)
>>>for i in lst_iter:
>>>    print(i)
    
1
2
3

模块collections中的类型Iterator就是迭代器的抽象基类,所有的迭代器都是Iterator的实例。即如果一个对象是Iterator的实例,则说明此对象是迭代器。

from collections import Iterator

>>> isinstance(s,Iterator)
False

>>> isinstance(it_s,Iterator)
True

如何自己定义一个迭代器:

我们自定义的函数只要实现了

1.__next()__方法,该方法在每次被调用时不断返回下一个值,直到无法继续返回下一个值时抛出StopIteration异常(next(iterator)实际上调用的是iterator内部的__next()__方法)。

2.实现一个可迭代接口Iterable,迭代器类型Iterator继承自可迭代类型Iterable,可迭代Iterable继承自object基类,迭代器Iterator类型包含__iter()____next()__方法,而可迭代类型Iteratble仅仅包含__iter__()。可迭代对象,通过__iter()__返回一个迭代器对象,迭代器对象的__next()__方法则实际用于被循环。

class MyIter():

def __init__(self,max_value):
    self.current_value = 0
    self.max_value = max_value

def __iter__(self):
    return self

def __next__(self):
    if self.current_value < self.max_value:
        result = self.current_value
        self.current_value += 1
        return result
    else:
        raise StopIteration

读者们,可以自己去验证,是否可以用next方法返回下一个值,是否可以用for循环,是否属于itertor哦。

小结:

  1. 凡是可作用于for语句循环的对象都是Iterable可迭代类型。
  2. 凡是可作用于next()函数的对象都是Iterator迭代器类型。
  3. strtuplelistdictset等类型是Iterable可迭代类型,但不是Iterator迭代器;通过Iterable可迭代类型的__iter()__方法可以获得一个Iterator迭代器对象,从而使得它们可以被for语句循环。
  4. Pythonfor循环本质上就是通过调用Iterable可迭代对象的__iter()__方法获得一个Iterator迭代器对象,然后不断调用Iterator迭代器对象__next()__方法实现的。

生成器

python中生成器提供了一种方便的方法来实现迭代器,而不需要必须实现__iter__()__next__()两个迭代器方法。

生成器:一个生成器可以“生成”值。创建一个生成器其实就是创建一个函数而已,并没有太大的不同。

生成器的定义方式有两种,一种是调用生成器函数,一种是使用生成器表达式语法。

生成器函数是指在函数体中使用yield表达式仅返回结果的函数。

yield表达式仅在定义生成器函数时使用,因此只能用在函数定义的主体中。

在函数体中使用yield表达式会使该函数成为生成器函数。

当生成器函数被调用时,它返回一个称为生成器的迭代器,该迭代器由python自动生成。然后,生成器控制了生成器函数的执行。因为返回的生成器是一个迭代器,所以生成器函数的执行结果也就可以被循环。当生成器的的__next__方法被调用时,生成器函数的函数体内的语句开始执行,执行进行到第一个yield表达式时,立即将yield表达式的结果返回给生成器的调用者,同时将生成器函数内部的状态挂起。即保持生成器函数的执行进度,和生成器函数内的局部状态:包括局部变量的当前绑定,指令指针,内部计算栈和任何异常处理的状态。当生成器的再次调用__next__方法来时,生成器函数恢复执行,并再次执行到yield表达式返回结果再保持状态,直到无法再执行到yield表达式。此时生成器自动抛出StopIteration异常。

1.调用生成器函数:

>>>def my_generator():
    for i in range(10):
        yield i,i*2
        
>>>my_generator()
<generator object my_generator at 0x0000024DAC0A4A20>
>>>for i in my_generator():
>>>    print(i)
    
(0, 0)
(1, 2)
(2, 4)
(3, 6)
(4, 8)
(5, 10)
(6, 12)
(7, 14)
(8, 16)
(9, 18)

### 调用next方法
>>>g = my_generator()
>>>next(g)
(0, 0)
>>>next(g)
(1, 2)
>>>next(g)
(2, 4)
>>>next(g)
(3, 6)
>>>next(g)
(4, 8)
>>>next(g)
(5, 10)
>>>next(g)
(6, 12)
>>>next(g)
(7, 14)
>>>next(g)
(8, 16)
>>>next(g)
(9, 18)
>>>next(g)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

2.生成器表达式

除了使用生成器函数可以得到生成器,还可以生成器表达式得到生成器表达式。生成器表达式本身看起来像列表推导, 但不是用方括号而是用圆括号包围起来:

>>>g2 = (x**2 for x in range(10))
>>>g2 
<generator object <genexpr> at 0x0000024DAC0A4930>
>>>t = (1,2,3,4,5)
>>>g3 = (x**2 for x in t)
>>>g3
<generator object <genexpr> at 0x0000024DAC0A4CF0>

和普通迭代器相比,生成器不单简化了迭代器的定义,还在使用效率上有提升。因为生成器在循环时,生成器函数每次只会返回一个结果,然后保持内部状态,所以生成器占用的内存是很小的。以下两个测试结果,第一个直接抛出MemoryError异常,第二个只能正确计算出结果(当然,如果用下面的例子的话,可能第一个你也能计算出来,但是,会很慢,而且内存占有率会瞬间上升很高,计算机会调用很大的资源来完成计算)。

# 全部数据先加载在1个列表上面,内存占用高
>>> s1 = sum([i for i in range(100000000)])
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    s1 = sum([i for i in range(100000000)])
  File "<pyshell#6>", line 1, in <listcomp>
    s1 = sum([i for i in range(100000000)])
MemoryError

# 数据几乎不占内存
>>> s2 = sum((i for i in range(100000000)))
>>> s2
4999999950000000

猜你喜欢

转载自blog.csdn.net/weixin_41571493/article/details/82428861