当前位置: 首页 > 后端技术 > Python

流畅的python学习笔记-第14章

时间:2023-03-25 23:08:24 Python

第14章可迭代对象、迭代器和生成器第14章可迭代对象、迭代器和生成器IterableObjectsIteration什么是迭代器迭代器迭代器内存消耗检测迭代器使用生成器yieldgeneratorgenerator执行过程generator创建generator判断yield和协程可迭代对象例如:importrere_word=re.compile(r'\w+')classSentence(object):def__init__(self,text):self.text=文本self.word=re_word.findall(text)def__getitem__(self,item):returnself.word[item]def__len__(self):returnlen(self.word)def__str__(self):return'Sentence(%s)'%self.wordif__name__=="__main__":s=Sentence("TodayisTuesday")print(s)forwordins:print(word)返回结果:Sentence(['Today','is','Tuesday'])TodayisTuesday我们知道一个对象可以迭代是因为实现了__iter__方法,但是__iter__方法没有实现加入句子。那为什么可以迭代。原因是iter和getitem在python中都是可迭代的。首先,它会检查iter方法是否被实现,如果被实现,则调用它,如果没有,但是__getitem__方法被实现了。Python将创建一个迭代器。尝试按顺序获取元素。如果尝试失败,将抛出类型错误异常,表明该对象不可迭代。因此:如果对象实现了返回迭代器的__iter__方法,则该对象是可迭代的。如果实现了__getitem__方法并且它的参数是一个从零开始的索引。这些对象也可以迭代。我们使用__iter__方法来转换前面的Sentence。在__iter__中返回一个可迭代对象iter(self.word)。执行forwordins时会调用__iter__方法.findall(text)def__iter__(self):返回iter(self.word)def__len__(self):returnlen(self.word)def__str__(self):return'Sentence(%s)'%self.wordif__name__=="__main__":s=Sentence("今天是星期二")print(s)forwordins:print(word)next的作用是返回下一个元素,如果没有元素,抛出stopIteration异常。下面我们就来看看如何使用吧。如果要遍历一个字符串,最简单的方法如下:s='abc'forcharins:print(char)如果不使用for方法,代码需要修改如下:s='abc'it=iter(s)whileTrue:try:print(next(it))exceptStopIteration:delitbreak首先将s变成一个iter对象,然后连续调用next获取下一个字符,如果没有的话字符,它将抛出StopIteration异常以释放对它的引用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__方法。首先通过iter让x成为一个可迭代对象,然后使用迭代器调用元素。使用迭代器的好处是:一次只从对象中读取一份数据,不会造成过多的内存开销。迭代器内存消耗检测可以看一下它的内存占用:使用python2.7.15+:importsysi=iter(range(1000000))printsys.getsizeof(i)r=range(1000000)printsys.getsizeof(r)y=xrange(1000000)#注意xrange和range的区别:xrange是范围迭代器的表达式printsys.getsizeof(y)resultreturn:56800006432usepython3.6+importsysi=iter(range(1000000))print(sys.getsizeof(i))r=range(1000000)print(sys.getsizeof(r))returnresult:3248这里有个问题:为什么python2和Python3的运行结果相差这么大?这是因为python3的内部机制已经将range转换成了迭代器。这里可以看出它适用于大数据,比如几千兆的数据,迭代器的内存占用大大减少,这是迭代器最大的优势。总结一下:简单说迭代器就是访问集合元素,迭代器是一个有next()方法的对象,而不是按索引计数。迭代器的使用那么我们如何访问迭代器中的元素呢?迭代器有两个方法,即iter()和next()方法。这两个方法是迭代器最基本的方法。一个用于获取迭代器对象,一个用于获取容器中的下一个元素。itertools是python提供的一个非常高效的模块,用于创建和使用迭代器fromitertoolsimportchaintest=chain.from_iterable('ABCDEFG')#print(test)#print(dir(test))#Viewclassspecificmethodprint(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'objecthasnoattribute'index'#Listl=range(3)print(l.index(2))#获取索引2,此时可以使用内置函数enumerate(),这个很重要。fori,jinenumerate(iter(['A','B','C'])):#fori,jinenumerate(['A','B','C']):print(i,j)运行结果返回:0A1B2C可以看到这个函数的源码是怎么写的classenumerate(Iterator[Tuple[int,_T]],Generic[_T]):def__init__(self,可迭代:可迭代[_T],开始:int=...)->无:...def__iter__(self)->Iterator[Tuple[int,_T]]:...如果sys.version_info>=(3,):def__next__(self)->Tuple[int,_T]:...else:defnext(self)->Tuple[int,_T]:..generators生成器是迭代器,但你只能遍历itonce(iterateoverthemonce)因为生成器不会把所有的值都放在内存中,而是实时生成这些值mygenerator=(x*xforxinrange(3))#Generator#mygenerator=[x*xforxinrange(3)]#listforiinmygenerator:print("i=",i)foriinmygenerator:print("New=",i)runningresult:i=0i=1i=4注意,不能在mygenerator中执行fori第二次,因为每个生成器只能使用一次yieldgenerator在Python中,使用generators可以轻松支持iterator协议。生成器由生成器函数生成。生成器函数可以通过常规def语句定义,但不是返回,yield一次返回一个结果,在每个结果之间暂停和继续它们的状态,以自动实现迭代协议。也就是说,yield是一个语法糖,内部实现支持iterator协议,yield内部是一个状态机,维持挂起和继续的状态。我们来看看生成器的使用:defZrange(n):i=0whileiprint([iforiinzrange])#[0,1,2]在这个例子中,定义了一个生成器函数,该函数返回一个生成器对象,然后可以通过for语句迭代访问它。事实上,生成器函数返回生成器迭代器。术语“生成器的迭代器”通常被称为“生成器”。请注意,生成器是一种特殊的迭代器。作为迭代器,生成器必须定义一些方法,其中之一就是next()。与迭代器一样,我们可以使用next()函数来获取下一个值。生成器执行过程示例:defZrange(n):print("beginofZrange")i=0whilei