python中int 3.14_流畅的python学习笔记-第14章

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

可迭代对象

举个例子:

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变成一个可迭代对象,然后使用迭代器来调用元素

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

迭代器内存消耗检测

可以看看它的内存占用:

使用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) #

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值