python迭代器、生成器-学习笔记整理

迭代器

迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
可迭代对象
我们把可以通过for…in…这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象 (Iterable)
判断一个对象是否为可迭代对象
使用 isinstance()

from collections import Iterable
isinstance([],Iterable)

在这里插入图片描述

  • 我们分析对可迭代对象进行迭代使用的过程,发现每迭代一次(即在for…in…中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为迭代器(Iterator)
  • 可迭代对象的本质就是可以向我们提供一个这样的中间“人”即迭代器帮助我们对其进行迭代遍历使用
  • 可迭代对象通过__iter__方法提供一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据

一个具备了__iter__方法的对象,就是一个可迭代对象。

from collections import Iterable
class Test1(object):
	def __init__(self):
		self.num_list = list()
		
	def add(self,num):
		self.num_list.append(num)
	
t1 = Test1()
t1.add(1)
t1.add(2)
print('t1是否为可迭代对象:',isinstance(t1,Iterable))

在这里插入图片描述

from collections import Iterable
class Test2(object):
	def __init__(self):
		self.num_list = list()
		
	def add(self,num):
		self.num_list.append(num)
		
	# 通过添加__iter__方法,使得一般对象变为一个可迭代对象。
	def __iter__(self):
		pass
	
t2 = Test2()
t2.add(1)
t2.add(2)
print('t2是否为可迭代对象:',isinstance(t2,Iterable))

在这里插入图片描述
iter()函数与next()函数

  • iter()函数可以获取可迭代对象的迭代器,iter()函数实际上就是调用了可迭代对象的__iter__方法。
  • next()函数来获取下一条数据

注意
当我们已经迭代完最后一个数据之后,再次调用next()函数会抛出StopIteration的异常,来告诉我们所有数据都已迭代完成,不用再执行next()函数了。

迭代器Iterator
迭代器是用来记录每次迭代访问到的位置,对迭代器使用next()函数的时候,迭代器会返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的__next__方法 (Python3中是对象的__next__方法,Python2中是对象的next()方法)。所以,构造一个迭代器,就要实现它的__next__方法。
但python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。

一个实现了__iter__方法和__next__方法的对象,就是迭代器。

from collections import Iterable,Iterator
class Test(object):
	def __init__(self):
		self.num_list = list()

	def add(self,num):
		self.num_list.append(num)
		
	# 实现了__iter__方法的对象,是一个可迭代对象
	def __iter__(self):
		# __iter__方法返回一个迭代器
		return TestIterator()

class TestIterator(object):
	def __init__(self):
		pass
	
	def __iter__(self):
		pass
		
	# 构造一个迭代器,就要实现它的__next__方法。
	def __next__(self):
		pass

t = Test()
print('t是否为可迭代对象',isinstance(t,Iterable))
# 通过iter()查看可迭代对象构建的迭代器
print(iter(t))
test_iterator = iter(t)
print('test_iterator是否为可迭代对象',isinstance(test_iterator,Iterator)) 

在这里插入图片描述

# 实现next取值
from collections import Iterable,Iterator
class Test(object):
	def __init__(self):
		self.num_list = list()

	def add(self,num):
		self.num_list.append(num)
		
	# 实现了__iter__方法的对象,是一个可迭代对象
	def __iter__(self):
		# __iter__方法返回一个迭代器
		return TestIterator(self)

class TestIterator(object):
	def __init__(self,obj):
		self.obj = obj
		# 定义一个计数的变量
		self.current_num = 0
	
	def __iter__(self):
		pass
		
	# 构造一个迭代器,就要实现它的__next__方法。
	def __next__(self):
		if self.current_num< len(self.obj.num_list):
			result = self.obj.num_list[self.current_num]
			self.current_num += 1
			return result
		else:
			# 迭代完最后一个数据之后,再次调用next()函数会抛出StopIteration的异常
			raise StopIteration

t = Test()
t.add(1)
t.add(2)
t.add(3)

for num in t:
	print(num)
# 等价为
class Test(object):
	def __init__(self):
		self.num_list = list()
		self.current_num = 0

	def add(self,num):
		self.num_list.append(num)
	# 但python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可
	def __iter__(self):
		return self

	def __next__(self):
		if self.current_num<len(self.num_list):
			result = self.num_list[self.current_num]
			self.current_num += 1
			return result
		else:
			raise StopIteration

t = Test()
t.add(1)
t.add(2)
t.add(3)

for num in t:
	print(num)

在这里插入图片描述
for…in…循环的本质
for item in Iterable 循环的:1、本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,2、然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,3、当遇到StopIteration的异常后循环结束。
迭代器的应用场景
迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值,如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。即迭代器保存的是生成数据的方式,节约内存。
range() 和xrange()区别
range

  • 函数说明:range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个列表。
    xrange
  • 函数说明:和range 的用法完全相同,但是返回的是一个生成器
    优点:但是要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间,这两个基本上都是在循环的时候用。再循环中尽量使用 xrange 这样性能可以得到提高,除非你要返回一个列表,用range

案例:斐波那契数列
斐波拉契数列(Fibonacci)
数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …

# 使用一般方法生成
def Fibonacci():
	a = 0
	b = 1
	nums = list()
	length = int(input('请输入Fibonacci数列最大长度:'))
	count = 0
	while count<length:
		nums.append(a)
		a,b = b,a+b
		count+=1
	return nums 

def main():
	Fibo_num = Fibonacci()
	for num in Fibo_num:
		print(num,end=' ')
	print('')

if __name__=='__main__':
	main()
	

在这里插入图片描述

# 使用迭代器
class Fibonacci(object):
	def __init__(self,n):
		self.nums = list()
		self.count = 0
		self.length = n  # # Fibonacci 长度
		self.num1 = 0  # Fibonacci 第一个数
		self.num2 = 1  # Fibonacci 第一个数

	def __iter__(self):
		return self

	def __next__(self):
		if self.count<self.length:
			num = self.num1
			self.num1,self.num2 = self.num2,self.num1+self.num2
			self.count+=1
			return num
		else:
			raise StopIteration

def main():
	length=int(input('请输入Fibonacci长度:'))
	f = Fibonacci(length)
	for nums in f:
		print(nums,end=' ')	
		
if __name__=='__main__':
	main()	

在这里插入图片描述

生成器

利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。
创建生成器

  • 把一个列表生成式的 [ ] 改成 ( )
    在这里插入图片描述
    对于列表,可以直接输出每一个元素,而对于生成器,可以按照迭代器的使用方法来使用,即可以通过next()函数、for循环、list()等方法使用
    在这里插入图片描述
  • 函数实现
def Fibonacci(length):
	a = 0
	b = 1
	count = 0 
	while count<length:
	# 若一个函数中存在yield,这个函数就是一个生成器
		yield a 
		a,b = b,a+b
		count+=1

def main():
	length = int(input('请输入Fibonacci长度:'))
	# 当调用存在yield的函数时,不再是函数调用,而会返回一个生成器对象
	obj = Fibonacci(length)
	for num in obj:
		print(num,end=' ')

if __name__=='__main__':
	main()

在这里插入图片描述
可以观察一下该函数的执行过程

def Fibonacci(length):
	a = 0
	b = 1
	count = 0 
	print('---1---')
	while count<length:
	# 若一个函数中存在yield,这个函数就是一个生成器
		print('---2---')
		yield a 
		print('---3---')
		a,b = b,a+b
		count+=1
		print('---4---')

def main():
	length = int(input('请输入Fibonacci长度:'))
	# 当调用存在yield的函数时,不再是执行函数调用,而会返回一个生成器对象
	obj = Fibonacci(length)
	for num in obj:
		print(num)

if __name__=='__main__':
	main()

在这里插入图片描述
若存在生成器中存在返回值,使用for循环调用生成器时,发现拿不到生成器的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

def Fibonacci(length):
	a = 0
	b = 1
	count = 0 
	while count<length:
	# 若一个函数中存在yield,这个函数就是一个生成器
		yield a 
		a,b = b,a+b
		count+=1
	return '生成完毕....'

def main():
	length = int(input('请输入Fibonacci长度:'))
	obj = Fibonacci(length)
	while True:
		try:
			res = next(obj)
			print(res)
		except Exception as result:
			print(result.value)
			break

if __name__=='__main__':
	main()

在这里插入图片描述
在使用生成器实现的方式中,将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。

  • 使用了yield关键字的函数不再是函数,而是生成器
  • yield关键字有两点作用:
    • 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
    • 将yield关键字后面表达式的值作为返回值返回(return的作用)
  • 可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
  • Python3中的生成器可以使用return返回最终运行的返回值,而Python2中的生成器不允许使用return返回一个返回值(即可以使用return从生成器中退出,但return后不能有任何表达式)。

send唤醒
除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。
使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。

def Fibonacci(length ):
	a = 0
	b = 1
	count = 0
	while count<length:
		# send()函数向断点处传入一个附加数据
		temp = yield a
		print(temp)
		a,b = b,a+b
		count+=1

fib = Fibonacci(10)
# 当send作为yield的第一次调用时,传入参数必须为None,next()相当于send(None)
res1 = next(fib)
print(res1)
res2 = fib.send('第二次调用:')
print(res2)
res3 = fib.send('第三次调用:')
print(res3)

在这里插入图片描述

协程

协程(Coroutine),又称微线程,纤程。协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。它自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。可以理解为:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定
在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。

import time 
def test1():
	while True:
		print('---1---')
		time.sleep(0.1)
		yield
		
def test2():
	while True:
		print('---2---')
		time.sleep(0.1)
		yield
	
def main():
	t1 = test1()
	t2 = test2()
	while True:
		next(t1)
		next(t2)

if __name__=='__main__':
	main()	

在这里插入图片描述
greenlet
python中通过greenlet模块对协程进行了封装
安装方式

sudo pip3 install greenlet
import greenlet
import time
def test1():
	while True:
		print('---1---')
		time.sleep(0.1)
		gr2.switch()
		
def test2():
	while True:
		print('---2---')
		time.sleep(0.1)
		gr1.switch()
	
gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)
# 切换到gr1中运行
gr1.switch()
	

在这里插入图片描述
gevent

sudo pip install gevent 

greenlet模块已经实现了协程,但需要人工切换
gevent模块能够自动切换任务

  • 原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
import gevent
def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)

print('----1----')
g1 = gevent.spawn(f, 5)
print('----2----')
g2 = gevent.spawn(f, 5)
print('----3----')
g3 = gevent.spawn(f, 5)
print('----4----')
g1.join()
g2.join()
g3.join()

在这里插入图片描述
3个greenlet是依次运行而不是交替运行

gevent切换执行
gevent遇到耗时操作gevent.sleep()会自动切换

import gevent
def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)
        
def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)
        
def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(0.5)

print('----1----')
g1 = gevent.spawn(f, 5)
print('----2----')
g2 = gevent.spawn(f, 5)
print('----3----')
g3 = gevent.spawn(f, 5)
print('----4----')
g1.join()
g2.join()
g3.join()

在这里插入图片描述
给程序打补丁

import gevent
import time
from gevent import monkey
# 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块
mokey.patch_all()

def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        
def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)
        
def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)

print('----1----')
g1 = gevent.spawn(f, 5)
print('----2----')
g2 = gevent.spawn(f, 5)
print('----3----')
g3 = gevent.spawn(f, 5)
print('----4----')
g1.join()
g2.join()
g3.join()

在这里插入图片描述
进程、线程、协程

  • 进程是资源分配的单位
  • 线程是操作系统调度的单位
  • 进程切换需要的资源很最大,效率很低
  • 线程切换需要的资源一般,效率一般(当然了在不考虑GIL(全局解释器锁)的情况下)
  • 协程切换任务资源很小,效率高
  • 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中所以是并发
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值