Python 并发2:迭代器,生成器,携程

携程

学习路线: 迭代器 -> 生成器 -> yield -> greenlet -> gevent

迭代器

迭代大家都知道, for i in XX, 时访问集合元素的一种方式。迭代器是一个可以记住遍历位置的对象,他从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只会向前进而不是向后退

Python的可迭代对象很普遍,如 list, tuple, str, dict等等。当然不可迭代的类型如int等也很普遍,我们来看一下报错, 清楚的告诉你这个对象not in iterable(不可迭代)

>>> for i in 5:
...     print(i)
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

如何判断一个数据类型是否可迭代,即判断这个类是不是collection.iterable的子类即可,Iterable对象都具有**iter()方法与next()**方法,可以被for循环 。

# 我们来定义一个可以被迭代的类
class Iter_son():
	def __init__(self):
		self.name = []
	def get_name(self):
		return self.name
	# 方法A: 如果不想考虑继承的话根据上面说的,我们要自己创建iter方法才可
	def __iter__(self):
		pass
	def __next__(self):
		pass
m = Iter_son()
for i in m:
    print(i)

# 可以看到,打印的错误信息不一样了,因为我瞎写了iter方法所以报错
TypeError: iter() returned non-iterator of type None

迭代器的实现

for的本质就是通过__next__的迭代获取我们所需要的数据,因此迭代器可以采用以下的方法实现

# 我们来定义一个可以被迭代的类
class Iter_son():
	def __init__(self):
		self.name = ['老王','老杨','阿炳']
		self.num = 3
	def new_student(self,name):
		self.name.append(name)
		self.num += 1
	def __iter__(self):
	#返回具有Iter方法和Next方法对象的引用, 我的话就一般返回本对象就好了,这样就会直接找他的next方法了
		return self
	def __next__(self):
		if self.a < self.num:
			num = self.a
			self.a += 1
			return num,self.name[num]
		# StopIteration 在 next() 方法中设置完成指定循环次数后触发 StopIteration 异常结束迭代。
		# StopIteration(教程):https://www.runoob.com/python3/python3-iterator-generator.html
		else:
			raise StopIteration    
m = Iter_son()
for a,b in m:
    print(str(a),str(b))
0 老王
1 老杨
2 阿炳

总结, for的逻辑:

  • 先判断for的对象是不是 Iterable的子类, 或是否具备 iter方法
  • 之后判断iter返回的引用对象中是否有__next__方法,返回next方法的返回值, 否则报错

可以迭代的类称为迭代器: Iterator

拓展: Iterator (https://blog.csdn.net/appleyuchi/article/details/109139809)

Iterator是Iterable的子类,Iterator对象都具有__iter__()和__next__()方法,Iterator可以通过next()获取其下一个数据。Iterator的生成方式是通过iter()方法,或为类定义__iter__()和__next__(),从而使类成为Iterator。

>>> from collections import Iterable
# 判断
>>> isinstance({1:1,2:2},Iterable)
True
# isinstance的意思是“判断类型”;isinstance()是一个内置函数,用于判断一个对象是否是一个已知的类型,类似type()
# isinstance() 与 type() 区别:type() 不会认为子类是一种父类类型,不考虑继承关系。isinstance() 会认为子类是一种父类类型,考虑继承关系。
https://blog.csdn.net/weixin_29358723/article/details/112047579

那迭代器有啥用,用range迭代不香么

先来一个问题镇楼,如果你要进行一个100000000以内的迭代循环,你会怎么做?

基本来说我们做循环的时候喜欢用for i in range(XXXX), 在python3本质上是没毛病的,但是对于python2即以下,我们需要考虑以下range的机制。range的机制是先生成一个列表,再去遍历 直接使用的话就要至少占去 4Byte * len( XXXX)的空间。 这么大的空间对我,们来说是一种极大的边了,所以我们课以考虑 i = 0 while (i<XXXX): i += 1 这样的遍历,将空间缩为4Byte, 但是如果要求强制用for解决怎么办呢,这个时候我们可以选择xrange。 xrange本身就是一个迭代器,在你使用的数据的时候,或者说循环到哪里,生成哪里。当然,python3 range == xrange

这也体现了迭代器的优点: 存储生成迭代的方法而非一个可迭代对象,消耗空间小

eg. 斐波那契数列迭代器:

class Fibonacci_Iter():
    def __init__ (self):
        self.num1 = 1
        self.num2 = 1
        self.round_num = 0
        self.maxs_round_num = 10
    def set_len(self,lens):
        self.maxs_round_num = lens    
    def __next__(self):
        if self.round_num <= 1:
            self.round_num += 1
            return 1
        elif self.round_num < self.maxs_round_num:
            self.round_num += 1
            num12 = self.num1 + self.num2
            if self.num1 < self.num2:
                self.num1 = num12
            else:
                self.num2 = num12
            return self.num12
        else:
            raise StopIteration
    def __iter__(self):
        return self

lists = Fibonacci_Iter()
lists.set_len(5)
for i in lists:
    print(i)
1
1
2
3
5
生成器

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

可惜俺靠blog : https://www.cnblogs.com/chengxuyuanaa/p/13065794.html

生成器的两种使用方式:

  • 小括号法

    >>> n1 = [x for x in range(5)]
    >>> n2 = (x for x in range(5))
    >>> n1
    [0, 1, 2, 3, 4]
    >>> n2
    <generator object <genexpr> at 0x0000029E1CA56B30>
    

    () 创建了一个生成器,而非一个实体的list()对象,节省了数据空间。不常用

  • 定义函数生成器

    # 使用生成器完成斐波那契数列
    
    def create_num(all_num):
    	a,b = 0,1
    	current_num = 0
    	while current_num < all_num:
    		# 定义生成器的传参, 这会变成生成器
    		yield a
    		a,b = b, a+b
    		current_num += 1
    # 因为函数变成了生成器,所以本身已经不是函数了,而是生成器对象
    obj = create_num(5)
    for num in obj:
    	print(num)
    	
    0
    1
    2
    3
    5
    

    在创建生成器对象obj时create num函数遇到yiled(这个时候current还是0)时会阻塞,指导for调用的时候生成器就拿出阻塞的a , 并在继续执行到下一个yiled(这个时候current = 1),继续阻塞,而for 去除就继续执行。每次只有一个a生成,同时也没有连边等数据结构占用空间。这样的方式减小了空间的消耗。

    同时,我们不管可以用for取值,也可以用next, send 的方式来进行唤醒

    def create_num2(all_num):
    	a,b = 0,1
    	current_num = 0
    	while current_num < all_num:
    		# 定义生成器的传参, 这会变成生成器
    		h = yield a
            if h is None:
                pass
           	else:
                print(h)
    		a,b = b, a+b
    		current_num += 1
    # 实例化生成器对象
    b = create_num(10)
    #注意next与send调用的区别
    print(next(b))
    0
    c = b.send('Hello')
    hello
    print(c)
    1
    

同时注意,由于生成器是一个类,所以实例化的时候会New对象,调用的时候不需要考虑不同对象之间的参数干涉

yield多任务

上面讲到, yiled可以进行阻塞,那么yiled在一定程度上不久可以进行多任务的平衡了么。

# 一个简单的多任务deemo
import time
def test():
    while True:
        name = yield None
        print("I'm task1")
        

if __name__ == '__main__':
    m = test()
    n = test()
    while (True):
        time.sleep(1)
        m.send("I'm task1")
        n.send("I'm task2")
        
TypeError: can't send non-None value to a just-started generators 

报错,可以发现yield的要求还是相当严苛的,原因见Bolg https://blog.csdn.net/chen1042246612/article/details/85317341。

简单来说就是第一个yield不允许传参,我们可以在第二个yield 或者先调用一次send方法

# 一个简单的多任务deemo2
import time
def test():
    while True:
        name = yield None
        print(name)
        

if __name__ == '__main__':
    m = test()
    next(m)
    n = test()
    next(n)
    while (True):
        time.sleep(1)
        m.send("I'm task1")
        n.send("I'm task2")
I'm task1
I'm task2
I'm task1
I'm task2

然而实际上yield实际上还是串行运行,但是不用创建进程线程,几乎不占用资源。不过是多个循环可以伪一起执行(甚至还是单线程你不能有更高要求)罢了

Greenlet, Gevent 携程多任务

Greenlet 本身与yield 底层无差别,不过greenlet对yield进行了封装,不需要额外在函数中写入yield,而Gevent对Greenlet进行了再次封装,不在赘述: https://blog.csdn.net/appleyuchi/article/details/79078736

# 简单Greenlet用法
from greenlet import greenlet
import time
 
def A():
    while 1:
        print('-------A-------')
        time.sleep(0.5)
        g2.switch()
 
def B():
    while 1:
        print('-------B-------')
        time.sleep(0.5)
        g1.switch()
 
g1 = greenlet(A)  #创建协程g1
g2 = greenlet(B)
 
g1.switch()  #跳转至协程g1

# 简单Gevent(遇到延时才切换,不遇到延时不切换)
import gevent
 
def A():
    while 1:
        print('-------A-------')
        gevent.sleep(1) #用来模拟一个耗时操作,注意不是time模块中的sleep
 
def B():
    while 1:
        print('-------B-------')
        gevent.sleep(0.5)  #每当碰到耗时操作,会自动跳转至其他协程
 
g1 = gevent.spawn(A) # 创建一个协程
g2 = gevent.spawn(B)
g1.join()  #等待协程执行结束
g2.join()

进程,线程,携程总结

  • 多线程,多进程根据CPU的荷属不一样可能时并行的,但是携程在一个线程中所以时并发的。
  • 携程切换的资源消耗最少,其次时线程,最大的时进程
  • 然而可笑的是,在Python中(除非你编译器特殊)进程是唯一实现并行的,效率应该是最快的。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值