第14章 可迭代的对象、迭代器和生成器

<!-- TOC -->

<!-- /TOC -->

可迭代对象

举个例子:

import re

re_word = re.compile(r'\w+')


class Sentence(object):
    def __init__(self, text):
        self.text = text
        self.word = re_word.findall(text)

    def __getitem__(self, item):
        return self.word[item]

    def __len__(self):
        return len(self.word)

    def __str__(self):
        return 'Sentence(%s)' % self.word


if __name__ == "__main__":

    s = Sentence("Today is Tuesday")
    print(s)

    for word in s:
        print(word)

返回结果:

Sentence(['Today', 'is', 'Tuesday'])
Today
is
Tuesday

我们知道一个对象可以迭代是因为实现了__iter__方法,
但是在Sentence中并没有实现__iter__方法。那为什么可以迭代呢。

原因在于在python中实现了iter和getitem的都是可迭代的。首先会检查是否实现了iter方法,如果实现了则调用,如果没有但是实现了__getitem__方法。

Python就会创建一个迭代器。

尝试按照顺序获取元素。如果尝试失败则会抛出typeerror异常,提示object is not iterable.

因此:

如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的。

如果实现了__getitem__方法,而且其参数是从零开始的索引。

这种对象也可以迭代。

我们用__iter__方法来改造之前的Sentence。
__iter__中返回一个可迭代对象iter(self.word)。
当执行for word in s的时候就会调用__iter__方法

import re

re_word = re.compile(r'\w+')


class Sentence(object):
    def __init__(self, text):
        self.text = text
        self.word = re_word.findall(text)

    def __iter__(self):
        return iter(self.word)

    def __len__(self):
        return len(self.word)

    def __str__(self):
        return 'Sentence(%s)' % self.word


if __name__ == "__main__":

    s = Sentence("Today is Tuesday")
    print(s)

    for word in s:
        print(word)

再来看下next, next的作用是返回下一个元素,如果没有元素了,抛出stopIteration异常。

我们来看下next的使用方法。如果要遍历一个字符串,最简单的方法如下:

s = 'abc'

for char in s:
    print(char)

如果不用for方法,代码需要修改如下:

s = 'abc'

it = iter(s)

while True:
    try:
        print(next(it))
    except StopIteration:
        del it
        break

首先将s变成一个iter对象,然后不断调用next获取下一个字符,如果没有字符了,则会抛出StopIteration异常释放对it的引用

s = 'abc'

it = iter(s)

print(next(it))
print(next(it))
print(next(it))
print(next(it)) # StopIteration

因为只有3个字符,但是调用了4次it.next()导致已经找不到字符因此抛出异常。

总结:

可迭代对象:实现了__iter__方法,就是可迭代的,可以返回自身作为迭代器。
也可以返回其他一个可迭代对象

迭代器

迭代器是什么

迭代器:在Python2中实现了next方法,在python3中实现了__next__方法。

首先要让x通过iter变成一个可迭代对象,然后使用迭代器来调用元素

使用迭代器好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。

Alt text

迭代器内存消耗检测

可以看看它的内存占用:

使用python2.7.15+:

import sys
i = iter(range(1000000))
print sys.getsizeof(i)


r = range(1000000)
print sys.getsizeof(r)


y = xrange(1000000)    # 注意 xrange跟range的区别: xrange是range的迭代器的表达方式
print sys.getsizeof(y)

结果返回:

56
8000064
32

使用python 3.6+

import sys
i = iter(range(1000000))
print (sys.getsizeof(i))


r = range(1000000)
print (sys.getsizeof(r))

返回结果:

32
48

这里有个问题:为什么在python2跟Python3的运行结果相差这么大呢?
这是因为python3内部机制已经将range转换成了一个迭代器了。

这里可以看的出来适合大的数据,比如好几个G的数据, 使用了迭代器 内存使用大幅度减少,这是迭代器最大的优点。

总结下:
我们简单说迭代器就是访问集合元素,迭代器就是有一个next()方法的对象,而不是通过索引来计数的。

迭代器使用

那么我们怎么能访问迭代器里面的元素呢?

迭代器有两个方法 ,分别是iter()和next()方法

这两个方法是迭代器最基本的方法
一个用来获得迭代器对象,一个用来获取容器中的下一个元素。

itertools是python提供的非常高效创建与使用迭代器的模块

from itertools import chain
test = chain.from_iterable('ABCDEFG')
# print(test)
# print(dir(test)) # 查看类具体方法

print(test.__next__())  # 'A'
print(test.__next__())  # 'B'
print(test.__next__())  # 'C'

test2 = chain('AB', 'CDE', 'F')
print(list(test2))  # ['A', 'B', 'C', 'D', 'E', 'F']

我们知道迭代器是不支持索引的,原因就是索引需实现明元素确占用的内存地址,而迭代器是用到元素的时候才会创建。如下:

i = iter(range(3))  # 创建迭代器
# i.index(2)  # 获取元素为2的索引
# AttributeError: 'range_iterator' object has no attribute 'index'

# 列表
l = range(3)
print(l.index(2))  # 获取索引2

这个时候可以使用内建函数enumerate(),这个函数很重要。

for i, j in enumerate(iter(['A', 'B', 'C'])):
    # for i, j in enumerate(['A', 'B', 'C']):
    print(i, j)

运行结果返回:

0 A
1 B
2 C


可以看下这个函数的源码是怎么写的

class enumerate(Iterator[Tuple[int, _T]], Generic[_T]):
    def __init__(self, iterable: Iterable[_T], start: int = ...) -> None: ...
    def __iter__(self) -> Iterator[Tuple[int, _T]]: ...
    if sys.version_info >= (3,):
        def __next__(self) -> Tuple[int, _T]: ...
    else:
        def next(self) -> Tuple[int, _T]: ..

生成器

生成器是迭代器,但你只能遍历它一次(iterate over them once)

因为生成器并没有将所有值放入内存中,而是实时地生成这些值

mygenerator = (x * x for x in range(3))  # 生成器
# mygenerator = [x * x for x in range(3)] # 列表
for i in mygenerator:
    print("i=", i)

for i in mygenerator:
    print("New=", i)

运行结果:

i= 0
i= 1
i= 4

注意,你不能执行for i in mygenerator第二次,因为每个生成器只能被使用一次

yield生成器

在Python中,使用生成器可以很方便的支持迭代器协议。

生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,
而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。

也就是说,yield是一个语法糖,内部实现支持了迭代器协议

同时yield内部是一个状态机,维护着挂起和继续的状态。

下面看看生成器的使用:

def Zrange(n):
    i = 0
    while i < n:
        yield i
        i += 1


zrange = Zrange(3)
print(zrange)  # <generator object Zrange at 0x000002997DE75468>
print([i for i in zrange])  # [0, 1, 2]

在这个例子中,定义了一个生成器函数,函数返回一个生成器对象,然后就可以通过for语句进行迭代访问了。

其实,生成器函数返回生成器的迭代器。 “生成器的迭代器”这个术语通常被称作”生成器”。
要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是next()。

如同迭代器一样,我们可以使用next()函数来获取下一个值。

生成器执行过程

例子:

def Zrange(n):
    print("begin of Zrange")
    i = 0
    while i < n:
        print("before yield:", i)
        yield i
        i += 1
        print("after yield:", i)

    print("begin of Zrange")


zrange = Zrange(3)
# print(zrange)

print(zrange.__next__())
print("-" * 10)
print(zrange.__next__())
print("-" * 10)
print(zrange.__next__())
# print(zrange.__next__()) # StopIteration

运行结果:

begin of Zrange
before yield: 0
0
----------
after yield: 1
before yield: 1
1
----------
after yield: 2
before yield: 2
2

通过结果可以看到:

  • 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有 执行。
  • 当next()方法第一次被调用的时候,生成器函数才开始执行,执行到yield语句处停止
  • next()方法的返回值就是yield语句处的参数(yielded value)
  • 当继续调用next()方法的时候,函数将接着上一次停止的yield语句处继续执行,并到下一个yield处停止;如果后面没有yield就抛出StopIteration异常

总结:生成器是迭代器的一种,但功能方法比迭代器多

生成器创建

生成器的创建可以使用yield关键字, 也可以使用生成器表达式

(x*2 for i in range(10))

生成器判断

判断是否为生成器函数可用isgeneratorfunction, 判断是否为生成器对象可用isgenerator

from inspect import isgeneratorfunction, isgenerator

g = (i for i in range(3))
print(isgenerator(g))  # True


def demo():
    yield 1


print(isgenerator(demo()))  # True
print(isgeneratorfunction(demo()))  # False
print(isgeneratorfunction(demo))  # True

yield与协程

def coroutine():
    print("coroutine start...")
    result = None
    while True:
        s = yield result
        result = 'result:{}'.format(s)


c = coroutine()
c.send(None)  # coroutine start...

print(c.send("first"))  # result:first
print(c.send("second"))  # result:second

c.close()

# c.send("hello")  # StopIteration

王知晓
37 声望19 粉丝

知晓知晓,上知天文下晓地理