当我们需要处理一个大量的数据集合时,一次性将其全部读入内存并处理可能会导致内存溢出。此时,我们可以采用迭代器Iterator
和生成器Generator
的方法,逐个地处理数据,从而避免内存溢出的问题。
迭代器是一个可以逐个访问元素的对象,它实现了python
的迭代协议,即实现了__iter__()
和__next__()
方法。通过调用__next__()
方法,我们可以逐个访问迭代器中的元素,直到所有元素都被访问完毕,此时再次调用__next__()
方法会引发StopIteration
异常。
生成器是一种特殊的迭代器,它的实现方式更为简洁,即通过yield
语句来实现。生成器函数使用yield
语句返回值,当生成器函数被调用时,它会返回一个生成器对象,通过调用__next__()
方法来逐个访问生成器中的元素,直到所有元素都被访问完毕,此时再次调用__next__()
方法会引发StopIteration
异常。
使用迭代器和生成器可以有效地避免内存溢出问题,并且代码实现也更为简洁、高效。在python中,很多内置函数和语言特性都支持迭代器和生成器的使用,例如for循环、列表推导式、生成器表达式等。
3.1 使用迭代器 迭代器可以通过内置函数iter()
进行创建,同时可以使用next()
函数获取下一个元素,如果迭代器没有更多的元素,则抛出StopIteration
异常在for
循环中,迭代器可以自动实现例如for x in my_iterable:
语句就可以遍历my_iterable
对象的所有元素。此外python
中还有一种特殊的迭代器,称为生成器(generator
),生成器是一种用简单的方法实现迭代器的方式,使用了yield
语句,生成器在执行过程中可以暂停并继续执行,而函数则是一旦开始执行就会一直执行到返回。
创建基本迭代器: 首先声明列表,然后使用__iter__
将其转为迭代器,并通过__next__
遍历迭代对象.
>>> list = [1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ]>>> >>> item = list .__iter__()>>> type (item)<class 'list_iterator' > >>> >>> item.__next__()1 >>> next (item)2
迭代器遍历日志文件: 使用迭代器可以实现对文本文件或日志的遍历,该方式可以遍历大型文件而不会出现卡死现象.
>>> with open ("passwd.log" ) as fp:... try :... while True :... print (next (fp))... except StopIteration:... print ("none" )>>> with open ("passwd.log" ) as fp:... while True :... line = next (fp,None )... if line is None :... break ... print (line)
循环遍历迭代元素: 由于迭代器遍历结束会报错,所以要使用try语句抛出一个StopIteration
结束异常.
>>> listvar = ["吕洞宾" , "张果老" , "蓝采和" , "特乖离" , "和香菇" , "汉钟离" , "王文" ]>>> item = listvar.__iter__()>>> >>> while True :... try :... temp = next (item)... print (temp)... except StopIteration:... break
迭代器与数组之间互转: 通过使用enumerate方法,并将列表转为迭代器对象,然后将对象转为制定格式.
>>> listvar = ["吕洞宾" , "张果老" , "蓝采和" , "特乖离" , "和香菇" , "汉钟离" , "王文" ]>>> >>> iter = enumerate (listvar) >>> dict = tuple (iter ) >>> dict ((0 , '吕洞宾' ), (1 , '张果老' ), (2 , '蓝采和' ), (3 , '特乖离' ), (4 , '和香菇' ), (5 , '汉钟离' ), (6 , '王文' )) >>> >>> dict = list (iter )>>> dict [(0 , '吕洞宾' ), (1 , '张果老' ), (2 , '蓝采和' ), (3 , '特乖离' ), (4 , '和香菇' ), (5 , '汉钟离' ), (6 , '王文' )]
3.2 使用生成器 生成器是一种可以动态生成数据的迭代器,不同于列表等容器类型一次性把所有数据生成并存储在内存中,生成器可以在需要时动态生成数据,这样可以节省内存空间和提高程序效率.使用生成器可以通过for循环遍历序列、列表等容器类型,而不需要提前知道其中所有元素.生成器可以使用yield
关键字返回值,每次调用yield
会暂停生成器并记录当前状态,下一次调用时可以从上一次暂停的地方继续执行,而生成器的状态则保留在生成器对象内部.除了使用next()
函数调用生成器外,还可以使用send()
函数向生成器中发送数据,并在生成器内部使用yield
表达式接收发送的数据.
当我们调用一个生成器函数时,其实返回的是一个迭代器对象 只要表达式中使用了yield函数,通常将此类函数称为生成器(generator) 运行生成器时,每次遇到yield函数,则会自动保存并暂停执行,直到使用next()方法时,才会继续迭代 跟普通函数不同,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器
在学习生成器之前,需要一些前置知识,先来研究一下列表解析,列表解析是python迭代机制的一种应用,它常用于实现创建新的列表,因此要放置于[]中,列表解析非常灵活,可以用户快速创建一组相应规则的列表元素,且支持迭代操作.
列表生成式基本语法: 通过列表生成式,我们可以完成数据的生成与过滤等操作.
>>> ret = [item for item in range (30 ) if item >0 ]>>> print (ret)[1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 , 27 , 28 , 29 ] >>> >>> ret = [item for item in range (30 ) if item >3 ]>>> print (ret)[4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 , 27 , 28 , 29 ] >>> >>> ret = [item for item in range (30 ) if item%2 !=0 ]>>> ret[1 , 3 , 5 , 7 , 9 , 11 , 13 , 15 , 17 , 19 , 21 , 23 , 25 , 27 , 29 ]
列表式求阶乘: 通过列表解析式,来实现列表的迭代求阶乘,并且只打印大于2(if x>=2)
的数据.
>>> var = [1 ,2 ,3 ,4 ,5 ]>>> retn = [ item ** 2 for item in var ]>>> retn[1 , 4 , 9 , 16 , 25 ] >>> >>> retn = [ item ** 2 for item in var if item >= 2 ]>>> retn[4 , 9 , 16 , 25 ] >>> >>> retn = [ (item**2 )/2 for item in range (1 ,10 ) ]>>> retn[0.5 , 2.0 , 4.5 , 8.0 , 12.5 , 18.0 , 24.5 , 32.0 , 40.5 ]
数据转换: 通过使用列表生成式,实现将一个字符串转换成一个合格的列表.
>>> String = "a,b,c,d,e,f,g,h" >>> List = [item for item in String.split("," )]>>> List ['a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' ]
数据合并: 通过列表解析式,实现迭代将两个列表按照规律合并.
>>> temp1=["x" ,"y" ,"z" ]>>> temp2=[1 ,2 ,3 ]>>> temp3=[ (i,j) for i in temp1 for j in temp2 ]>>> temp3[('x' , 1 ), ('x' , 2 ), ('x' , 3 ), ('y' , 1 ), ('y' , 2 ), ('y' , 3 ), ('z' , 1 ), ('z' , 2 ), ('z' , 3 )]
文件过滤: 通过使用列表解析,实现文本的过滤操作.
>>> import os>>> file_list=os.listdir("/var/log" )>>> file_log=[ i for i in file_list if i.endswith(".log" ) ]>>> print (file_log)['boot.log' , 'yum.log' , 'ecs_network_optimization.log' , 'ntp.log' ] >>> file_log=[ i for i in os.listdir("/var/log" ) if i.endswith(".log" ) ]>>> print (file_log)['boot.log' , 'yum.log' , 'ecs_network_optimization.log' , 'ntp.log' ]
接下来我们就来研究一下生成器吧,生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器.
我们先来看以下两种情况的对比,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建了一个生成器.
>>> lis = [x*x for x in range (10 )]>>> print (lis)[0 , 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 ] >>> generator = (x*x for x in range (10 ))>>> print (generator)<generator object <genexpr> at 0x0000022E5C788A98 >
如上例子,第一个lis通过列表生成式,创建了一个列表,而第二个generator
则打印出一个内存地址,如果我们想获取到第二个变量中的数据,则需要迭代操作,如下所示:
>>> generator = (x*x for x in range (10 ))>>> print (next (generator))0 >>> print (next (generator))1 >>> print (next (generator))4 >>> print (next (generator))9
以上可以看到,generator保存的是算法,每次调用next(generaotr),就计算出他的下一个元素的值,直到计算出最后一个元素,使用for循环可以简便的遍历出迭代器中的数据,因为generator也是可迭代对象.
>>> generator = (x*x for x in range (10 ))>>> >>> for i in generator: print (i,end="" ) 0149162536496481
生成器表达式并不真正创建数字列表,而是返回一个生成器对象,此对象在每次计算出一个条目后,把这个条目"产生"(yield)
出来,生成器表达式使用了”惰性计算”或称作”延迟求值”的机制序列过长,并且每次只需要获取一个元素时,应当考虑使用生成器表达式而不是列表解析.
>>> import sys>>> >>> yie=( i**2 for i in range (1 ,10 ) )>>> next (yie)1 >>> next (yie)4 >>> next (yie)9 >>> for j in ( i**2 for i in range (1 ,10 )):print (j/2 )
3.3 队列的使用 队列是一个多线程编程中常用的数据结构,它提供了一种可靠的方式来安全地传递数据和控制线程间的访问. 在多线程环境下,如果没有同步机制,多个线程同时访问共享资源,可能会导致数据混乱或者程序崩溃.而Queue队列就是一种线程安全的数据结构,它提供了多个线程访问和操作的接口,可以保证多个线程之间的数据安全性和顺序性. 通过Queue队列,一个线程可以将数据放入队列,而另一个线程则可以从队列中取出数据进行处理,实现了线程之间的通信和协调.
先进先出队列: 先来介绍简单的队列例子,以及队列的常用方法的使用,此队列是先进先出模式.
import queueq = queue.Queue(5 ) print (q.empty()) q.put(1 ) q.put(2 ) q.put(3 ,block=False ,timeout=2 ) print (q.full()) print (q.qsize()) print (q.maxsize) print (q.get(block=False ,timeout=2 )) print (q.get()) q.task_done() print (q.get())q.task_done() q.join()
import queuedef show (q,i ): if q.empty() or q.qsize() >= 1 : q.put(i) elif q.full(): print ('queue not size' ) que = queue.Queue(5 ) for i in range (5 ): show(que,i) print ('queue is number:' ,que.qsize()) for j in range (5 ): print (que.get()) print ('......end' )
后进先出队列: 这个队列则是,后进先出,也就是最后放入的数据最先弹出,类似于堆栈.
>>> import queue>>> >>> q = queue.LifoQueue()>>> >>> q.put("wang" )>>> q.put("rui" )>>> q.put("ni" )>>> q.put("hao" )>>> >>> print (q.get())hao >>> print (q.get())ni >>> print (q.get())rui >>> print (q.get())wang >>> print (q.get())
优先级队列: 此类队列,可以指定优先级顺序,默认从高到低排列,以此根据优先级弹出数据.
>>> import queue>>> >>> q = queue.PriorityQueue()>>> >>> q.put((1 ,"python1" ))>>> q.put((-1 ,"python2" ))>>> q.put((10 ,"python3" ))>>> q.put((4 ,"python4" ))>>> q.put((98 ,"python5" ))>>> >>> print (q.get())(-1 , 'python2' ) >>> print (q.get())(1 , 'python1' ) >>> print (q.get())(4 , 'python4' ) >>> print (q.get())(10 , 'python3' ) >>> print (q.get())(98 , 'python5' )
双向的队列: 双向队列,也就是说可以分别从两边弹出数据,没有任何限制.
>>> import queue>>> >>> q = queue.deque()>>> >>> q.append(1 )>>> q.append(2 )>>> q.append(3 )>>> q.append(4 )>>> q.append(5 )>>> >>> q.appendleft(6 )>>> >>> print (q.pop())5 >>> print (q.pop())4 >>> print (q.popleft())6 >>> print (q.popleft())1 >>> print (q.popleft())2
生产者消费者模型: 生产者消费者模型,是各种开发场景中最常用的开发模式,以下是模拟的模型.
import queue,timeimport threadingq = queue.Queue() def productor (arg ): while True : q.put(str (arg)) print ("%s 号窗口有票...." %str (arg)) time.sleep(1 ) def consumer (arg ): while True : print ("第 %s 人取 %s 号窗口票" %(str (arg),q.get())) time.sleep(1 ) for i in range (10 ): t = threading.Thread(target=productor,args=(i,)) t.start() for j in range (5 ): t = threading.Thread(target=consumer,args=(j,)) t1 = threading.Thread(target=consumer,args=(j,)) t.start() t1.start()